@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.
- package/package.json +12 -3
- package/.ai/generators/_template.ts +0 -37
- package/.ai/generators/abstract.ts +0 -24
- package/.ai/generators/actor-task-form-filler.ts +0 -140
- package/.ai/generators/actor-task.ts +0 -122
- package/.ai/generators/auth-core.ts +0 -126
- package/.ai/generators/browser-runtime.ts +0 -114
- package/.ai/generators/cli-command.ts +0 -96
- package/.ai/generators/core-framework.ts +0 -80
- package/.ai/generators/docs.ts +0 -92
- package/.ai/generators/error-logging.ts +0 -102
- package/.ai/generators/extraction-helper.ts +0 -96
- package/.ai/generators/interaction-behavior.ts +0 -129
- package/.ai/generators/site-actor.ts +0 -125
- package/.ai/generators/site-login-flow.ts +0 -117
- package/.ai/generators/unit-test.ts +0 -109
- package/.ai/workflows/_template.ts +0 -20
- package/.ai/workflows/starter.ts +0 -20
- package/ai-gen.config.ts +0 -67
- package/src/auth/AuthStateDetector.ts +0 -18
- package/src/auth/CredentialsProvider.ts +0 -48
- package/src/auth/LoginFlow.ts +0 -332
- package/src/auth/LoginFlow.types.ts +0 -141
- package/src/auth/SessionStore.ts +0 -21
- package/src/auth/index.ts +0 -5
- package/src/browser/BrowserFactory.ts +0 -253
- package/src/browser/BrowserSession.ts +0 -50
- package/src/browser/PuppeteerLike.ts +0 -65
- package/src/browser/RuntimeConfig.ts +0 -152
- package/src/browser/index.ts +0 -5
- package/src/browser/profileValidation.ts +0 -73
- package/src/cli/run.ts +0 -112
- package/src/core/Actor.ts +0 -167
- package/src/core/ActorContext.ts +0 -34
- package/src/core/ActorRegistry.ts +0 -26
- package/src/core/ActorRunner.ts +0 -240
- package/src/core/defineActor.ts +0 -5
- package/src/core/index.ts +0 -5
- package/src/errors/AuthError.ts +0 -7
- package/src/errors/AutomationError.ts +0 -26
- package/src/errors/ConfigError.ts +0 -7
- package/src/errors/ExtractionError.ts +0 -7
- package/src/errors/NavigationError.ts +0 -7
- package/src/errors/SelectorError.ts +0 -10
- package/src/errors/index.ts +0 -6
- package/src/extraction/Extractor.ts +0 -65
- package/src/extraction/Pagination.ts +0 -47
- package/src/extraction/index.ts +0 -2
- package/src/index.ts +0 -9
- package/src/interaction/FieldClearer.ts +0 -73
- package/src/interaction/Forms.ts +0 -27
- package/src/interaction/GhostCursorAdapter.ts +0 -79
- package/src/interaction/HumanInteractor.ts +0 -32
- package/src/interaction/HumanTyping.ts +0 -157
- package/src/interaction/NativePuppeteerInteractor.ts +0 -68
- package/src/interaction/Navigation.ts +0 -37
- package/src/interaction/PageAdapter.ts +0 -86
- package/src/interaction/Waits.ts +0 -5
- package/src/interaction/index.ts +0 -9
- package/src/logging/ConsoleLogger.ts +0 -44
- package/src/logging/Logger.ts +0 -15
- package/src/logging/MemoryLogger.ts +0 -34
- package/src/logging/NullLogger.ts +0 -8
- package/src/logging/index.ts +0 -4
- package/src/sites/example/example.actor.ts +0 -53
- package/src/sites/example/example.selectors.ts +0 -17
- package/src/sites/example/example.types.ts +0 -18
- package/src/sites/example/index.ts +0 -3
- package/src/sites/index.ts +0 -3
- package/src/sites/myvistage-com/index.ts +0 -3
- package/src/sites/myvistage-com/login-action-list.json +0 -349
- package/src/sites/myvistage-com/myvistage-com.actor.ts +0 -50
- package/src/sites/myvistage-com/myvistage-com.selectors.ts +0 -14
- package/src/sites/myvistage-com/myvistage-com.types.ts +0 -18
- package/src/sites/myvistage-com/post-comment-action.json +0 -81
- package/src/sites/upwork-com/index.ts +0 -6
- package/src/sites/upwork-com/upwork-com.actor.ts +0 -97
- package/src/sites/upwork-com/upwork-com.runner.ts +0 -17
- package/src/sites/upwork-com/upwork-com.selectors.ts +0 -10
- package/src/sites/upwork-com/upwork-com.types.ts +0 -102
- package/src/sites/upwork-com/upwork-com.util.ts +0 -41
- package/src/utils/delay.ts +0 -4
- package/src/utils/index.ts +0 -5
- package/src/utils/invariant.ts +0 -7
- package/src/utils/redact.ts +0 -53
- package/src/utils/retry.ts +0 -31
- package/src/utils/url.ts +0 -7
- package/tests/fixtures/FakeCredentialsProvider.ts +0 -12
- package/tests/fixtures/FakeCursor.ts +0 -48
- package/tests/fixtures/FakePage.ts +0 -266
- package/tests/fixtures/makeContext.ts +0 -76
- package/tests/unit/auth/AuthStateDetector.test.ts +0 -80
- package/tests/unit/auth/LoginFlow.test.ts +0 -296
- package/tests/unit/browser/BrowserFactory.test.ts +0 -370
- package/tests/unit/core/ActorRunner.test.ts +0 -370
- package/tests/unit/core/defineActor.test.ts +0 -112
- package/tests/unit/extraction/Extractor.test.ts +0 -48
- package/tests/unit/extraction/Pagination.test.ts +0 -54
- package/tests/unit/interaction/FieldClearer.test.ts +0 -29
- package/tests/unit/interaction/Forms.test.ts +0 -35
- package/tests/unit/interaction/GhostCursorAdapter.test.ts +0 -68
- package/tests/unit/interaction/HumanTyping.test.ts +0 -54
- package/tests/unit/interaction/NativePuppeteerInteractor.test.ts +0 -22
- package/tests/unit/interaction/PageAdapter.test.ts +0 -25
- package/tests/unit/logging/redact.test.ts +0 -36
- package/tests/unit/sites/myvistage-com.actor.test.ts +0 -19
- package/tests/unit/sites/myvistage-com.login.test.ts +0 -22
- package/tests/unit/sites/myvistage-com.postComment.test.ts +0 -70
- package/tests/unit/sites/upwork-com.login.test.ts +0 -52
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -22
- 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
|
-
});
|