@epsilon-asi/actors 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/package.json +12 -3
  2. package/.ai/generators/_template.ts +0 -37
  3. package/.ai/generators/abstract.ts +0 -24
  4. package/.ai/generators/actor-task-form-filler.ts +0 -140
  5. package/.ai/generators/actor-task.ts +0 -122
  6. package/.ai/generators/auth-core.ts +0 -126
  7. package/.ai/generators/browser-runtime.ts +0 -114
  8. package/.ai/generators/cli-command.ts +0 -96
  9. package/.ai/generators/core-framework.ts +0 -80
  10. package/.ai/generators/docs.ts +0 -92
  11. package/.ai/generators/error-logging.ts +0 -102
  12. package/.ai/generators/extraction-helper.ts +0 -96
  13. package/.ai/generators/interaction-behavior.ts +0 -129
  14. package/.ai/generators/site-actor.ts +0 -125
  15. package/.ai/generators/site-login-flow.ts +0 -117
  16. package/.ai/generators/unit-test.ts +0 -109
  17. package/.ai/workflows/_template.ts +0 -20
  18. package/.ai/workflows/starter.ts +0 -20
  19. package/ai-gen.config.ts +0 -67
  20. package/src/auth/AuthStateDetector.ts +0 -18
  21. package/src/auth/CredentialsProvider.ts +0 -48
  22. package/src/auth/LoginFlow.ts +0 -332
  23. package/src/auth/LoginFlow.types.ts +0 -141
  24. package/src/auth/SessionStore.ts +0 -21
  25. package/src/auth/index.ts +0 -5
  26. package/src/browser/BrowserFactory.ts +0 -253
  27. package/src/browser/BrowserSession.ts +0 -50
  28. package/src/browser/PuppeteerLike.ts +0 -65
  29. package/src/browser/RuntimeConfig.ts +0 -152
  30. package/src/browser/index.ts +0 -5
  31. package/src/browser/profileValidation.ts +0 -73
  32. package/src/cli/run.ts +0 -112
  33. package/src/core/Actor.ts +0 -167
  34. package/src/core/ActorContext.ts +0 -34
  35. package/src/core/ActorRegistry.ts +0 -26
  36. package/src/core/ActorRunner.ts +0 -240
  37. package/src/core/defineActor.ts +0 -5
  38. package/src/core/index.ts +0 -5
  39. package/src/errors/AuthError.ts +0 -7
  40. package/src/errors/AutomationError.ts +0 -26
  41. package/src/errors/ConfigError.ts +0 -7
  42. package/src/errors/ExtractionError.ts +0 -7
  43. package/src/errors/NavigationError.ts +0 -7
  44. package/src/errors/SelectorError.ts +0 -10
  45. package/src/errors/index.ts +0 -6
  46. package/src/extraction/Extractor.ts +0 -65
  47. package/src/extraction/Pagination.ts +0 -47
  48. package/src/extraction/index.ts +0 -2
  49. package/src/index.ts +0 -9
  50. package/src/interaction/FieldClearer.ts +0 -73
  51. package/src/interaction/Forms.ts +0 -27
  52. package/src/interaction/GhostCursorAdapter.ts +0 -79
  53. package/src/interaction/HumanInteractor.ts +0 -32
  54. package/src/interaction/HumanTyping.ts +0 -157
  55. package/src/interaction/NativePuppeteerInteractor.ts +0 -68
  56. package/src/interaction/Navigation.ts +0 -37
  57. package/src/interaction/PageAdapter.ts +0 -86
  58. package/src/interaction/Waits.ts +0 -5
  59. package/src/interaction/index.ts +0 -9
  60. package/src/logging/ConsoleLogger.ts +0 -44
  61. package/src/logging/Logger.ts +0 -15
  62. package/src/logging/MemoryLogger.ts +0 -34
  63. package/src/logging/NullLogger.ts +0 -8
  64. package/src/logging/index.ts +0 -4
  65. package/src/sites/example/example.actor.ts +0 -53
  66. package/src/sites/example/example.selectors.ts +0 -17
  67. package/src/sites/example/example.types.ts +0 -18
  68. package/src/sites/example/index.ts +0 -3
  69. package/src/sites/index.ts +0 -3
  70. package/src/sites/myvistage-com/index.ts +0 -3
  71. package/src/sites/myvistage-com/login-action-list.json +0 -349
  72. package/src/sites/myvistage-com/myvistage-com.actor.ts +0 -50
  73. package/src/sites/myvistage-com/myvistage-com.selectors.ts +0 -14
  74. package/src/sites/myvistage-com/myvistage-com.types.ts +0 -18
  75. package/src/sites/myvistage-com/post-comment-action.json +0 -81
  76. package/src/sites/upwork-com/index.ts +0 -6
  77. package/src/sites/upwork-com/upwork-com.actor.ts +0 -97
  78. package/src/sites/upwork-com/upwork-com.runner.ts +0 -17
  79. package/src/sites/upwork-com/upwork-com.selectors.ts +0 -10
  80. package/src/sites/upwork-com/upwork-com.types.ts +0 -102
  81. package/src/sites/upwork-com/upwork-com.util.ts +0 -41
  82. package/src/utils/delay.ts +0 -4
  83. package/src/utils/index.ts +0 -5
  84. package/src/utils/invariant.ts +0 -7
  85. package/src/utils/redact.ts +0 -53
  86. package/src/utils/retry.ts +0 -31
  87. package/src/utils/url.ts +0 -7
  88. package/tests/fixtures/FakeCredentialsProvider.ts +0 -12
  89. package/tests/fixtures/FakeCursor.ts +0 -48
  90. package/tests/fixtures/FakePage.ts +0 -266
  91. package/tests/fixtures/makeContext.ts +0 -76
  92. package/tests/unit/auth/AuthStateDetector.test.ts +0 -80
  93. package/tests/unit/auth/LoginFlow.test.ts +0 -296
  94. package/tests/unit/browser/BrowserFactory.test.ts +0 -370
  95. package/tests/unit/core/ActorRunner.test.ts +0 -370
  96. package/tests/unit/core/defineActor.test.ts +0 -112
  97. package/tests/unit/extraction/Extractor.test.ts +0 -48
  98. package/tests/unit/extraction/Pagination.test.ts +0 -54
  99. package/tests/unit/interaction/FieldClearer.test.ts +0 -29
  100. package/tests/unit/interaction/Forms.test.ts +0 -35
  101. package/tests/unit/interaction/GhostCursorAdapter.test.ts +0 -68
  102. package/tests/unit/interaction/HumanTyping.test.ts +0 -54
  103. package/tests/unit/interaction/NativePuppeteerInteractor.test.ts +0 -22
  104. package/tests/unit/interaction/PageAdapter.test.ts +0 -25
  105. package/tests/unit/logging/redact.test.ts +0 -36
  106. package/tests/unit/sites/myvistage-com.actor.test.ts +0 -19
  107. package/tests/unit/sites/myvistage-com.login.test.ts +0 -22
  108. package/tests/unit/sites/myvistage-com.postComment.test.ts +0 -70
  109. package/tests/unit/sites/upwork-com.login.test.ts +0 -52
  110. package/tsconfig.build.json +0 -9
  111. package/tsconfig.json +0 -22
  112. package/vitest.config.ts +0 -12
@@ -1,68 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { GhostCursorAdapter } from '../../../src/interaction/GhostCursorAdapter.js';
3
- import { FakeGhostCursor } from '../../fixtures/FakeCursor.js';
4
- import { FakePage } from '../../fixtures/FakePage.js';
5
-
6
- describe('GhostCursorAdapter', () => {
7
- it('delegates clicks to ghost-cursor after waiting for the selector', async () => {
8
- const page = new FakePage().setSelector('#button');
9
- const cursor = new FakeGhostCursor();
10
- const adapter = new GhostCursorAdapter(page, { cursor });
11
-
12
- await adapter.click('#button', { cursorOptions: { waitForClick: 100 } });
13
-
14
- expect(page.waitedSelectors[0]?.selector).toBe('#button');
15
- expect(cursor.clicks).toEqual([{ selector: '#button', options: { waitForClick: 100 } }]);
16
- });
17
-
18
- it('delegates moves to ghost-cursor', async () => {
19
- const page = new FakePage().setSelector('#target');
20
- const cursor = new FakeGhostCursor();
21
- const adapter = new GhostCursorAdapter(page, { cursor });
22
-
23
- await adapter.move('#target', { cursorOptions: { moveSpeed: 700 } });
24
-
25
- expect(cursor.moves).toEqual([{ selector: '#target', options: { moveSpeed: 700 } }]);
26
- });
27
-
28
- it('types by clicking, selecting/deleting existing content, then sending keys one by one', async () => {
29
- const page = new FakePage().setSelector('#email', { value: 'old' });
30
- const cursor = new FakeGhostCursor();
31
- const sleeps: number[] = [];
32
- const adapter = new GhostCursorAdapter(page, {
33
- cursor,
34
- random: () => 0.5,
35
- sleep: async ms => { sleeps.push(ms); },
36
- typing: { intervalJitterMs: 0 }
37
- });
38
-
39
- await adapter.type('#email', 'new', { delayMs: 5 });
40
-
41
- expect(cursor.clicks.map(click => click.selector)).toContain('#email');
42
- expect(page.keyboard.pressed).toEqual([{ key: 'Backspace' }]);
43
- expect(page.keyboard.typed).toEqual([
44
- { text: 'n', options: { delay: 5 } },
45
- { text: 'e', options: { delay: 5 } },
46
- { text: 'w', options: { delay: 5 } }
47
- ]);
48
- expect(sleeps).toEqual([185, 185]);
49
- });
50
-
51
- it('can disable human typing and send the whole value at once', async () => {
52
- const page = new FakePage().setSelector('#email', { value: 'old' });
53
- const cursor = new FakeGhostCursor();
54
- const adapter = new GhostCursorAdapter(page, { cursor });
55
-
56
- await adapter.type('#email', 'new@example.com', { delayMs: 5, typing: { enabled: false } });
57
-
58
- expect(page.keyboard.typed).toEqual([{ text: 'new@example.com', options: { delay: 5 } }]);
59
- });
60
-
61
- it('scrolls an element into view', async () => {
62
- const page = new FakePage().setSelector('#item');
63
- const cursor = new FakeGhostCursor();
64
- const adapter = new GhostCursorAdapter(page, { cursor });
65
-
66
- await expect(adapter.scrollIntoView('#item')).resolves.toBeUndefined();
67
- });
68
- });
@@ -1,54 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import {
3
- characterIntervalMsForWordsPerMinute,
4
- HumanTyper,
5
- jitteredIntervalMs,
6
- normalizeHumanTypingOptions
7
- } from '../../../src/interaction/HumanTyping.js';
8
- import { FakeKeyboard } from '../../fixtures/FakePage.js';
9
-
10
- describe('HumanTyper', () => {
11
- it('computes the 65 WPM cadence using five-character words', () => {
12
- expect(characterIntervalMsForWordsPerMinute(65, 5)).toBeCloseTo(184.615, 3);
13
- });
14
-
15
- it('types key-by-key and waits between characters at approximately 65 WPM', async () => {
16
- const keyboard = new FakeKeyboard();
17
- const sleeps: number[] = [];
18
- const typer = new HumanTyper({
19
- random: () => 0.5,
20
- sleep: async ms => { sleeps.push(ms); }
21
- });
22
-
23
- await typer.type(keyboard, 'abcd', { intervalJitterMs: 0 });
24
-
25
- expect(keyboard.typed.map(record => record.text)).toEqual(['a', 'b', 'c', 'd']);
26
- expect(sleeps).toEqual([185, 185, 185]);
27
- });
28
-
29
- it('applies small symmetric jitter to the inter-key interval', () => {
30
- const base = characterIntervalMsForWordsPerMinute(65, 5);
31
-
32
- expect(jitteredIntervalMs(base, 18, 20, () => 0)).toBe(167);
33
- expect(jitteredIntervalMs(base, 18, 20, () => 0.5)).toBe(185);
34
- expect(jitteredIntervalMs(base, 18, 20, () => 1)).toBe(203);
35
- });
36
-
37
- it('can fall back to bulk Puppeteer typing when human typing is disabled', async () => {
38
- const keyboard = new FakeKeyboard();
39
- const sleeps: number[] = [];
40
- const typer = new HumanTyper({
41
- sleep: async ms => { sleeps.push(ms); }
42
- });
43
-
44
- await typer.type(keyboard, 'hello', { enabled: false }, 7);
45
-
46
- expect(keyboard.typed).toEqual([{ text: 'hello', options: { delay: 7 } }]);
47
- expect(sleeps).toEqual([]);
48
- });
49
-
50
- it('validates typing configuration', () => {
51
- expect(() => normalizeHumanTypingOptions({ targetWordsPerMinute: 0 })).toThrow(RangeError);
52
- expect(() => normalizeHumanTypingOptions({ intervalJitterMs: -1 })).toThrow(RangeError);
53
- });
54
- });
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { NativePuppeteerInteractor } from '../../../src/interaction/NativePuppeteerInteractor.js';
3
- import { FakePage } from '../../fixtures/FakePage.js';
4
-
5
- describe('NativePuppeteerInteractor', () => {
6
- it('uses the same key-by-key typing and select-delete clear behavior without ghost-cursor', async () => {
7
- const page = new FakePage().setSelector('#field', { value: 'old' });
8
- const sleeps: number[] = [];
9
- const interactor = new NativePuppeteerInteractor(page, {
10
- random: () => 0.5,
11
- sleep: async ms => { sleeps.push(ms); },
12
- typing: { intervalJitterMs: 0 }
13
- });
14
-
15
- await interactor.type('#field', 'ok');
16
-
17
- expect(page.clicked).toEqual(['#field']);
18
- expect(page.keyboard.pressed).toEqual([{ key: 'Backspace' }]);
19
- expect(page.keyboard.typed.map(record => record.text)).toEqual(['o', 'k']);
20
- expect(sleeps).toEqual([185]);
21
- });
22
- });
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { PuppeteerPageAdapter } from '../../../src/interaction/PageAdapter.js';
3
- import { FakePage } from '../../fixtures/FakePage.js';
4
-
5
- describe('PuppeteerPageAdapter', () => {
6
- it('normalizes text content', async () => {
7
- const fakePage = new FakePage().setSelector('#title', { textContent: ' Hello\n World ' });
8
- const page = new PuppeteerPageAdapter(fakePage);
9
-
10
- await expect(page.text('#title')).resolves.toBe('Hello World');
11
- });
12
-
13
- it('returns false when exists times out', async () => {
14
- const page = new PuppeteerPageAdapter(new FakePage());
15
-
16
- await expect(page.exists('#missing')).resolves.toBe(false);
17
- });
18
-
19
- it('reads attributes', async () => {
20
- const fakePage = new FakePage().setSelector('a', { attributes: { href: '/x' } });
21
- const page = new PuppeteerPageAdapter(fakePage);
22
-
23
- await expect(page.attr('a', 'href')).resolves.toBe('/x');
24
- });
25
- });
@@ -1,36 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { redact } from '../../../src/utils/redact.js';
3
-
4
- describe('redact', () => {
5
- it('redacts nested sensitive values without mutating the original object', () => {
6
- const input = {
7
- username: 'alice',
8
- password: 'secret',
9
- nested: {
10
- authorization: 'Bearer token',
11
- safe: 'value'
12
- },
13
- items: [
14
- { sessionCookie: 'abc123' },
15
- { name: 'visible' }
16
- ]
17
- };
18
-
19
- const output = redact(input);
20
-
21
- expect(output).toEqual({
22
- username: 'alice',
23
- password: '[REDACTED]',
24
- nested: {
25
- authorization: '[REDACTED]',
26
- safe: 'value'
27
- },
28
- items: [
29
- { sessionCookie: '[REDACTED]' },
30
- { name: 'visible' }
31
- ]
32
- });
33
- expect(input.password).toBe('secret');
34
- expect(input.nested.authorization).toBe('Bearer token');
35
- });
36
- });
@@ -1,19 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { myvistageComActor } from '../../../src/sites/myvistage-com/myvistage-com.actor.js';
3
-
4
- describe('myvistageComActor', () => {
5
- it('has expected metadata', () => {
6
- expect(myvistageComActor.id).toBe('myvistage-com');
7
- expect(myvistageComActor.baseUrl).toBe('https://myvistage.com');
8
- });
9
-
10
- it('ping task returns ok with null echo by default', async () => {
11
- const output = await myvistageComActor.tasks.ping({} as never, {});
12
- expect(output).toEqual({ ok: true, echo: null });
13
- });
14
-
15
- it('ping task echoes provided message', async () => {
16
- const output = await myvistageComActor.tasks.ping({} as never, { message: 'hello' });
17
- expect(output).toEqual({ ok: true, echo: 'hello' });
18
- });
19
- });
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { myvistageComActor } from '../../../src/sites/myvistage-com/myvistage-com.actor.js';
3
- import { myvistageComSelectors } from '../../../src/sites/myvistage-com/myvistage-com.selectors.js';
4
-
5
- describe('myvistage-com actor login flow', () => {
6
- it('uses a one-page username/password login definition', () => {
7
- const auth = myvistageComActor.auth;
8
-
9
- expect(auth).toBeDefined();
10
- expect(auth?.loginUrl).toBe('https://myvistage.com');
11
- expect(auth?.credentials).toEqual({ id: 'myvistage-com' });
12
- expect(auth?.selectors).toEqual(myvistageComSelectors.login);
13
- expect(auth?.steps).toBeUndefined();
14
- });
15
-
16
- it('keeps human-like typing behavior configured at the flow level', () => {
17
- expect(myvistageComActor.auth?.behavior?.typing).toEqual({
18
- targetWordsPerMinute: 65,
19
- intervalJitterMs: 18
20
- });
21
- });
22
- });
@@ -1,70 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { myvistageComActor } from '../../../src/sites/myvistage-com/myvistage-com.actor.js';
3
- import { myvistageComSelectors } from '../../../src/sites/myvistage-com/myvistage-com.selectors.js';
4
- import { makeContext } from '../../fixtures/makeContext.js';
5
-
6
- describe('myvistage-com actor postComment task', () => {
7
- it('fills fields in order, submits the form, and returns completion output', async () => {
8
- const { context, fakePage, fakeInteractor } = makeContext({
9
- actorId: 'myvistage-com',
10
- baseUrl: 'https://myvistage.com'
11
- });
12
-
13
- fakePage
14
- .setSelector(myvistageComSelectors.postComment.comment)
15
- .setSelector(myvistageComSelectors.postComment.subject)
16
- .setSelector(myvistageComSelectors.postComment.submit);
17
-
18
- const result = await myvistageComActor.tasks.postComment(context, {
19
- comment: 'Great insights, thank you for sharing.',
20
- subject: 'Quick follow-up'
21
- });
22
-
23
- expect(fakePage.gotos[0]?.url).toBe('https://myvistage.com/?status/151341-151341-1779377493/');
24
- expect(fakeInteractor.typed).toEqual([
25
- {
26
- selector: myvistageComSelectors.postComment.comment,
27
- value: 'Great insights, thank you for sharing.',
28
- options: { required: true }
29
- },
30
- {
31
- selector: myvistageComSelectors.postComment.subject,
32
- value: 'Quick follow-up',
33
- options: { required: false }
34
- }
35
- ]);
36
- expect(fakeInteractor.clicks).toEqual([{ selector: myvistageComSelectors.postComment.submit }]);
37
- expect(result).toEqual({
38
- submittedComment: 'Great insights, thank you for sharing.',
39
- submittedSubject: 'Quick follow-up'
40
- });
41
- });
42
-
43
- it('skips optional fields when not provided and still submits', async () => {
44
- const { context, fakePage, fakeInteractor } = makeContext({
45
- actorId: 'myvistage-com',
46
- baseUrl: 'https://myvistage.com'
47
- });
48
-
49
- fakePage
50
- .setSelector(myvistageComSelectors.postComment.comment)
51
- .setSelector(myvistageComSelectors.postComment.submit);
52
-
53
- const result = await myvistageComActor.tasks.postComment(context, {
54
- comment: 'Posting without a subject.'
55
- });
56
-
57
- expect(fakeInteractor.typed).toEqual([
58
- {
59
- selector: myvistageComSelectors.postComment.comment,
60
- value: 'Posting without a subject.',
61
- options: { required: true }
62
- }
63
- ]);
64
- expect(fakeInteractor.clicks).toEqual([{ selector: myvistageComSelectors.postComment.submit }]);
65
- expect(result).toEqual({
66
- submittedComment: 'Posting without a subject.',
67
- submittedSubject: null
68
- });
69
- });
70
- });
@@ -1,52 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { upworkComActor } from '../../../src/sites/upwork-com/upwork-com.actor.js';
3
- import { upworkComSelectors } from '../../../src/sites/upwork-com/upwork-com.selectors.js';
4
-
5
- describe('upwork-com actor login flow', () => {
6
- it('uses a staged username-first/password-second login definition', () => {
7
- const auth = upworkComActor.auth;
8
-
9
- expect(auth).toBeDefined();
10
- expect(auth?.loginUrl).toBe('https://www.upwork.com/ab/account-security/login');
11
- expect(auth?.credentials).toEqual({ id: 'upwork' });
12
- expect(auth?.selectors.loggedInSignal).toBe(upworkComSelectors.login.loggedInSignal);
13
- expect(auth?.selectors.errorMessage).toBe(upworkComSelectors.login.errorMessage);
14
-
15
- expect(auth?.steps).toEqual([
16
- {
17
- type: 'fill',
18
- name: 'username',
19
- selector: upworkComSelectors.login.username,
20
- credential: 'username'
21
- },
22
- {
23
- type: 'click',
24
- name: 'continue to password',
25
- selector: upworkComSelectors.login.continueToPassword,
26
- waitForSelector: upworkComSelectors.login.password,
27
- waitForSelectorTimeoutMs: 5_000
28
- },
29
- {
30
- type: 'fill',
31
- name: 'password',
32
- selector: upworkComSelectors.login.password,
33
- credential: 'password'
34
- },
35
- {
36
- type: 'click',
37
- name: 'submit password',
38
- selector: upworkComSelectors.login.submit,
39
- submit: true,
40
- waitForNavigation: true,
41
- checkForError: false
42
- }
43
- ]);
44
- });
45
-
46
- it('keeps human-like typing behavior configured at the flow level', () => {
47
- expect(upworkComActor.auth?.behavior?.typing).toEqual({
48
- targetWordsPerMinute: 65,
49
- intervalJitterMs: 18
50
- });
51
- });
52
- });
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "dist"
6
- },
7
- "include": ["src/**/*.ts"],
8
- "exclude": ["tests", "dist", "node_modules", "vitest.config.ts"]
9
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022", "DOM"],
7
- "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "exactOptionalPropertyTypes": true,
10
- "esModuleInterop": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "skipLibCheck": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true,
16
- "outDir": "dist",
17
- "rootDir": ".",
18
- "types": ["node", "vitest/globals"]
19
- },
20
- "include": ["src/**/*.ts", "tests/**/*.ts", "vitest.config.ts"],
21
- "exclude": ["dist", "node_modules"]
22
- }
package/vitest.config.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- globals: true,
7
- include: ['tests/**/*.test.ts'],
8
- restoreMocks: true,
9
- clearMocks: true,
10
- mockReset: true
11
- }
12
- });