@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,266 +0,0 @@
1
- import type { HTTPResponse, WaitForOptions, WaitForSelectorOptions } from 'puppeteer-core';
2
- import type { BrowserContextLike, BrowserLike, KeyboardLike, PageLike } from '../../src/browser/PuppeteerLike.js';
3
-
4
- interface FakeElementState {
5
- textContent?: string;
6
- attributes?: Record<string, string>;
7
- value?: string;
8
- isContentEditable?: boolean;
9
- selected?: boolean;
10
- focused?: boolean;
11
- events?: string[];
12
- selectionStart?: number;
13
- selectionEnd?: number;
14
- scrolled?: boolean;
15
- }
16
-
17
- class FakeElement {
18
- private readonly attributes: Record<string, string>;
19
-
20
- constructor(private readonly state: FakeElementState = {}) {
21
- this.attributes = state.attributes ?? {};
22
- this.state.events ??= [];
23
- }
24
-
25
- get textContent(): string {
26
- return this.state.textContent ?? '';
27
- }
28
-
29
- set textContent(value: string) {
30
- this.state.textContent = value;
31
- }
32
-
33
- get value(): string {
34
- return this.state.value ?? '';
35
- }
36
-
37
- set value(value: string) {
38
- this.state.value = value;
39
- }
40
-
41
- get isContentEditable(): boolean {
42
- return this.state.isContentEditable ?? false;
43
- }
44
-
45
- get events(): string[] {
46
- return this.state.events ?? [];
47
- }
48
-
49
- getAttribute(name: string): string | null {
50
- return this.attributes[name] ?? null;
51
- }
52
-
53
- dispatchEvent(event: Event): boolean {
54
- this.state.events ??= [];
55
- this.state.events.push(event.type);
56
- return true;
57
- }
58
-
59
- focus(): void {
60
- this.state.focused = true;
61
- }
62
-
63
- select(): void {
64
- this.state.selected = true;
65
- this.state.selectionStart = 0;
66
- this.state.selectionEnd = this.value.length;
67
- }
68
-
69
- setSelectionRange(start: number, end: number): void {
70
- this.state.selected = true;
71
- this.state.selectionStart = start;
72
- this.state.selectionEnd = end;
73
- }
74
-
75
- scrollIntoView(): void {
76
- this.state.scrolled = true;
77
- }
78
-
79
- querySelectorAll(selector: string): FakeElement[] {
80
- if (selector === 'thead th') {
81
- return this.attributes.__headers?.split('|').map(textContent => new FakeElement({ textContent })) ?? [];
82
- }
83
- if (selector === 'tbody tr') {
84
- const rowTexts = this.attributes.__rows?.split('\n').filter(Boolean) ?? [];
85
- return rowTexts.map(rowText => new FakeTableRow(rowText));
86
- }
87
- return [];
88
- }
89
- }
90
-
91
- class FakeTableRow extends FakeElement {
92
- constructor(private readonly rowText: string) {
93
- super();
94
- }
95
-
96
- override querySelectorAll(selector: string): FakeElement[] {
97
- if (selector !== 'td') return [];
98
- return this.rowText.split('|').map(textContent => new FakeElement({ textContent }));
99
- }
100
- }
101
-
102
- export class FakeKeyboard implements KeyboardLike {
103
- typed: Array<{ text: string; options?: { delay?: number } }> = [];
104
- pressed: Array<{ key: string; options?: { delay?: number } }> = [];
105
-
106
- async type(text: string, options?: { delay?: number }): Promise<void> {
107
- const record: { text: string; options?: { delay?: number } } = { text };
108
- if (options !== undefined) record.options = options;
109
- this.typed.push(record);
110
- }
111
-
112
- async press(key: string, options?: { delay?: number }): Promise<void> {
113
- const record: { key: string; options?: { delay?: number } } = { key };
114
- if (options !== undefined) record.options = options;
115
- this.pressed.push(record);
116
- }
117
- }
118
-
119
- export class FakePage implements PageLike {
120
- keyboard = new FakeKeyboard();
121
- readonly selectors = new Map<string, FakeElementState>();
122
- readonly selectorLists = new Map<string, FakeElementState[]>();
123
- readonly gotos: Array<{ url: string; options?: WaitForOptions }> = [];
124
- readonly waitedSelectors: Array<{ selector: string; options?: WaitForSelectorOptions }> = [];
125
- readonly waitedNavigations: WaitForOptions[] = [];
126
- readonly clicked: string[] = [];
127
- readonly focused: string[] = [];
128
- defaultTimeout?: number;
129
- defaultNavigationTimeout?: number;
130
- currentUrl = 'about:blank';
131
- pageTitle = 'Fake Page';
132
- pageContent = '<html></html>';
133
- screenshots: Record<string, unknown>[] = [];
134
-
135
- setSelector(selector: string, state: FakeElementState = {}): this {
136
- this.selectors.set(selector, state);
137
- return this;
138
- }
139
-
140
- setTextList(selector: string, texts: string[]): this {
141
- this.selectorLists.set(selector, texts.map(textContent => ({ textContent })));
142
- return this;
143
- }
144
-
145
- removeSelector(selector: string): this {
146
- this.selectors.delete(selector);
147
- return this;
148
- }
149
-
150
- async goto(url: string, options?: WaitForOptions): Promise<HTTPResponse | null> {
151
- this.currentUrl = url;
152
- const record: { url: string; options?: WaitForOptions } = { url };
153
- if (options !== undefined) record.options = options;
154
- this.gotos.push(record);
155
- return null;
156
- }
157
-
158
- async waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise<unknown> {
159
- const record: { selector: string; options?: WaitForSelectorOptions } = { selector };
160
- if (options !== undefined) record.options = options;
161
- this.waitedSelectors.push(record);
162
- if (!this.selectors.has(selector) && !this.selectorLists.has(selector)) {
163
- throw new Error(`Missing selector: ${selector}`);
164
- }
165
- return {};
166
- }
167
-
168
- async waitForNavigation(options?: WaitForOptions): Promise<HTTPResponse | null> {
169
- this.waitedNavigations.push(options ?? {});
170
- return null;
171
- }
172
-
173
- async click(selector: string): Promise<void> {
174
- await this.waitForSelector(selector);
175
- this.clicked.push(selector);
176
- }
177
-
178
- async focus(selector: string): Promise<void> {
179
- await this.waitForSelector(selector);
180
- this.focused.push(selector);
181
- }
182
-
183
- async $eval<T>(selector: string, pageFunction: (element: Element, ...args: unknown[]) => T, ...args: unknown[]): Promise<T> {
184
- const state = this.selectors.get(selector);
185
- if (state === undefined) throw new Error(`Missing selector: ${selector}`);
186
- return pageFunction(new FakeElement(state) as unknown as Element, ...args);
187
- }
188
-
189
- async $$eval<T>(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => T, ...args: unknown[]): Promise<T> {
190
- const states = this.selectorLists.get(selector) ?? (this.selectors.has(selector) ? [this.selectors.get(selector)!] : []);
191
- const elements = states.map(state => new FakeElement(state) as unknown as Element);
192
- return pageFunction(elements, ...args);
193
- }
194
-
195
- async evaluate<T>(pageFunction: (...args: unknown[]) => T, ...args: unknown[]): Promise<T> {
196
- return pageFunction(...args);
197
- }
198
-
199
- url(): string {
200
- return this.currentUrl;
201
- }
202
-
203
- async title(): Promise<string> {
204
- return this.pageTitle;
205
- }
206
-
207
- async content(): Promise<string> {
208
- return this.pageContent;
209
- }
210
-
211
- async screenshot(options?: Record<string, unknown>): Promise<Uint8Array | string> {
212
- this.screenshots.push(options ?? {});
213
- return new Uint8Array();
214
- }
215
-
216
- setDefaultTimeout(timeout: number): void {
217
- this.defaultTimeout = timeout;
218
- }
219
-
220
- setDefaultNavigationTimeout(timeout: number): void {
221
- this.defaultNavigationTimeout = timeout;
222
- }
223
- }
224
-
225
- export class FakeBrowserContext implements BrowserContextLike {}
226
-
227
- export class FakeBrowser implements BrowserLike {
228
- readonly context = new FakeBrowserContext();
229
- readonly closeCalls: number[] = [];
230
- readonly disconnectCalls: number[] = [];
231
- defaultBrowserContextCalls = 0;
232
- newPageCalls = 0;
233
- createBrowserContextCalls = 0;
234
-
235
- constructor(private readonly pageList: FakePage[] = [new FakePage()]) {}
236
-
237
- defaultBrowserContext(): BrowserContextLike {
238
- this.defaultBrowserContextCalls += 1;
239
- return this.context;
240
- }
241
-
242
- async pages(): Promise<PageLike[]> {
243
- return this.pageList;
244
- }
245
-
246
- async newPage(): Promise<PageLike> {
247
- this.newPageCalls += 1;
248
- const page = new FakePage();
249
- this.pageList.push(page);
250
- return page;
251
- }
252
-
253
- // Deliberately not part of BrowserLike. Tests assert BrowserFactory never calls it.
254
- async createBrowserContext(): Promise<BrowserContextLike> {
255
- this.createBrowserContextCalls += 1;
256
- return new FakeBrowserContext();
257
- }
258
-
259
- async close(): Promise<void> {
260
- this.closeCalls.push(Date.now());
261
- }
262
-
263
- async disconnect(): Promise<void> {
264
- this.disconnectCalls.push(Date.now());
265
- }
266
- }
@@ -1,76 +0,0 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { BrowserSession } from '../../src/browser/BrowserSession.js';
5
- import type { RuntimeConfig } from '../../src/browser/RuntimeConfig.js';
6
- import type { ActorContext, AuthController } from '../../src/core/ActorContext.js';
7
- import { Extractor } from '../../src/extraction/Extractor.js';
8
- import { Pagination } from '../../src/extraction/Pagination.js';
9
- import { FormFiller } from '../../src/interaction/Forms.js';
10
- import type { HumanInteractor } from '../../src/interaction/HumanInteractor.js';
11
- import { Navigator } from '../../src/interaction/Navigation.js';
12
- import { PuppeteerPageAdapter } from '../../src/interaction/PageAdapter.js';
13
- import { MemoryLogger } from '../../src/logging/MemoryLogger.js';
14
- import { FakeHumanInteractor } from './FakeCursor.js';
15
- import { FakeBrowser, FakePage } from './FakePage.js';
16
-
17
- export interface TestContextParts {
18
- context: ActorContext;
19
- fakePage: FakePage;
20
- fakeInteractor: FakeHumanInteractor;
21
- logger: MemoryLogger;
22
- config: RuntimeConfig;
23
- }
24
-
25
- export function makeContext(overrides: Partial<{
26
- page: FakePage;
27
- interactor: HumanInteractor;
28
- actorId: string;
29
- baseUrl: string;
30
- auth: AuthController;
31
- }> = {}): TestContextParts {
32
- const fakePage = overrides.page ?? new FakePage();
33
- const fakeInteractor = overrides.interactor instanceof FakeHumanInteractor
34
- ? overrides.interactor
35
- : new FakeHumanInteractor();
36
- const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'paf-test-context-'));
37
- fs.mkdirSync(path.join(userDataDir, 'Default'));
38
- const config: RuntimeConfig = {
39
- browser: {
40
- mode: 'existing-profile',
41
- userDataDir,
42
- profileDirectory: 'Default',
43
- headless: false
44
- }
45
- };
46
- const fakeBrowser = new FakeBrowser([fakePage]);
47
- const session = new BrowserSession(fakeBrowser, fakeBrowser.context, fakePage, config.browser, 'launched');
48
- const page = new PuppeteerPageAdapter(fakePage);
49
- const cursor = overrides.interactor ?? fakeInteractor;
50
- const forms = new FormFiller(page, cursor);
51
- const nav = new Navigator(page, cursor, overrides.baseUrl ?? 'https://example.com');
52
- const extract = new Extractor(page);
53
- const pagination = new Pagination(page, cursor);
54
- const logger = new MemoryLogger();
55
-
56
- const auth: AuthController = overrides.auth ?? {
57
- isLoggedIn: async () => false,
58
- ensureAuthenticated: async () => undefined
59
- };
60
-
61
- const context: ActorContext = {
62
- actor: { id: overrides.actorId ?? 'example', baseUrl: overrides.baseUrl ?? 'https://example.com' },
63
- config,
64
- session,
65
- page,
66
- cursor,
67
- forms,
68
- nav,
69
- extract,
70
- pagination,
71
- auth,
72
- logger
73
- };
74
-
75
- return { context, fakePage, fakeInteractor, logger, config };
76
- }
@@ -1,80 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { AuthStateDetector } from '../../../src/auth/AuthStateDetector.js';
3
- import { defineLoginFlow } from '../../../src/auth/LoginFlow.types.js';
4
- import { makeContext } from '../../fixtures/makeContext.js';
5
-
6
- describe('AuthStateDetector', () => {
7
- it('returns true when the logged-in signal exists', async () => {
8
- const { context, fakePage } = makeContext();
9
- fakePage.setSelector('[data-testid="account"]');
10
- const detector = new AuthStateDetector();
11
-
12
- const result = await detector.isLoggedIn(context, defineLoginFlow({
13
- loginUrl: '/login',
14
- selectors: {
15
- username: '#email',
16
- password: '#password',
17
- submit: '#submit',
18
- loggedInSignal: '[data-testid="account"]'
19
- }
20
- }));
21
-
22
- expect(result).toBe(true);
23
- });
24
-
25
- it('returns false when the logged-in signal is absent', async () => {
26
- const { context } = makeContext();
27
- const detector = new AuthStateDetector();
28
-
29
- await expect(detector.isLoggedIn(context, defineLoginFlow({
30
- loginUrl: '/login',
31
- selectors: {
32
- username: '#email',
33
- password: '#password',
34
- submit: '#submit',
35
- loggedInSignal: '#missing'
36
- }
37
- }))).resolves.toBe(false);
38
- });
39
-
40
- it('visits authCheckUrl before checking auth state', async () => {
41
- const { context, fakePage } = makeContext();
42
- fakePage.setSelector('#account');
43
- const detector = new AuthStateDetector();
44
-
45
- await detector.isLoggedIn(context, defineLoginFlow({
46
- loginUrl: '/login',
47
- selectors: {
48
- username: '#email',
49
- password: '#password',
50
- submit: '#submit',
51
- loggedInSignal: '#account'
52
- },
53
- behavior: {
54
- authCheckUrl: '/dashboard'
55
- }
56
- }));
57
-
58
- expect(fakePage.gotos[0]?.url).toBe('https://example.com/dashboard');
59
- });
60
-
61
- it('uses custom verifyLoggedIn hook when provided', async () => {
62
- const { context } = makeContext();
63
- const detector = new AuthStateDetector();
64
-
65
- const result = await detector.isLoggedIn(context, defineLoginFlow({
66
- loginUrl: '/login',
67
- selectors: {
68
- username: '#email',
69
- password: '#password',
70
- submit: '#submit',
71
- loggedInSignal: '#account'
72
- },
73
- hooks: {
74
- verifyLoggedIn: async () => true
75
- }
76
- }));
77
-
78
- expect(result).toBe(true);
79
- });
80
- });