@scenetest/scenes 0.2.0 → 0.4.0
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/dist/__tests__/dsl.test.js +20 -0
- package/dist/__tests__/dsl.test.js.map +1 -1
- package/dist/__tests__/keyboard.test.d.ts +2 -0
- package/dist/__tests__/keyboard.test.d.ts.map +1 -0
- package/dist/__tests__/keyboard.test.js +332 -0
- package/dist/__tests__/keyboard.test.js.map +1 -0
- package/dist/__tests__/markdown-scene.test.js +85 -5
- package/dist/__tests__/markdown-scene.test.js.map +1 -1
- package/dist/__tests__/reactive.test.js +259 -6
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/runner.test.d.ts +2 -0
- package/dist/__tests__/runner.test.d.ts.map +1 -0
- package/dist/__tests__/runner.test.js +182 -0
- package/dist/__tests__/runner.test.js.map +1 -0
- package/dist/actor.d.ts +21 -4
- package/dist/actor.d.ts.map +1 -1
- package/dist/actor.js +176 -12
- package/dist/actor.js.map +1 -1
- package/dist/builtin-macros.d.ts +87 -0
- package/dist/builtin-macros.d.ts.map +1 -0
- package/dist/builtin-macros.js +154 -0
- package/dist/builtin-macros.js.map +1 -0
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -1
- package/dist/devices.d.ts +6 -0
- package/dist/devices.d.ts.map +1 -1
- package/dist/devices.js +13 -0
- package/dist/devices.js.map +1 -1
- package/dist/dsl.d.ts +4 -0
- package/dist/dsl.d.ts.map +1 -1
- package/dist/dsl.js +18 -1
- package/dist/dsl.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts +168 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +370 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/markdown-scene.d.ts +4 -2
- package/dist/markdown-scene.d.ts.map +1 -1
- package/dist/markdown-scene.js +21 -9
- package/dist/markdown-scene.js.map +1 -1
- package/dist/reactive.d.ts +25 -2
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +163 -16
- package/dist/reactive.js.map +1 -1
- package/dist/runner.d.ts +11 -5
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +141 -35
- package/dist/runner.js.map +1 -1
- package/dist/swarm.d.ts +1 -1
- package/dist/swarm.d.ts.map +1 -1
- package/dist/swarm.js +8 -5
- package/dist/swarm.js.map +1 -1
- package/dist/team-manager.d.ts +48 -3
- package/dist/team-manager.d.ts.map +1 -1
- package/dist/team-manager.js +150 -15
- package/dist/team-manager.js.map +1 -1
- package/dist/types.d.ts +133 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { runCleanup, runSetup } from '../runner.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function makeScene(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
name: 'test scene',
|
|
9
|
+
file: '/test/scene.spec.md',
|
|
10
|
+
fn: async () => { },
|
|
11
|
+
cleanup: [],
|
|
12
|
+
setup: [],
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const team = {
|
|
17
|
+
learner: { key: 'learner-123', username: 'alice', email: 'alice@test.com' },
|
|
18
|
+
friend: { key: 'friend-456', username: 'bob', email: 'bob@test.com' },
|
|
19
|
+
};
|
|
20
|
+
const teamMeta = {
|
|
21
|
+
name: 'Kannada Team',
|
|
22
|
+
tags: { lang: 'kan', region: 'asia' },
|
|
23
|
+
};
|
|
24
|
+
const testStart = '2026-04-02T10:00:00.000Z';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// runCleanup
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
describe('runCleanup', () => {
|
|
29
|
+
it('does nothing when cleanup is empty', async () => {
|
|
30
|
+
const scene = makeScene();
|
|
31
|
+
const server = { supabase: { ran: false } };
|
|
32
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
33
|
+
expect(server.supabase.ran).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
it('evaluates a single cleanup expression with server in scope', async () => {
|
|
36
|
+
const calls = [];
|
|
37
|
+
const server = {
|
|
38
|
+
db: {
|
|
39
|
+
delete: (table) => { calls.push(`delete:${table}`); return Promise.resolve(); },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const scene = makeScene({ cleanup: ["db.delete('user_deck')"] });
|
|
43
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
44
|
+
expect(calls).toEqual(["delete:user_deck"]);
|
|
45
|
+
});
|
|
46
|
+
it('runs all expressions when multiple cleanup: lines specified', async () => {
|
|
47
|
+
const calls = [];
|
|
48
|
+
const server = {
|
|
49
|
+
db: {
|
|
50
|
+
delete: (table) => { calls.push(`delete:${table}`); return Promise.resolve(); },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const scene = makeScene({
|
|
54
|
+
cleanup: ["db.delete('comments')", "db.delete('notifications')"],
|
|
55
|
+
});
|
|
56
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
57
|
+
expect(calls).toEqual(['delete:comments', 'delete:notifications']);
|
|
58
|
+
});
|
|
59
|
+
it('interpolates [role.field] tokens', async () => {
|
|
60
|
+
let capturedKey = '';
|
|
61
|
+
const server = {
|
|
62
|
+
db: {
|
|
63
|
+
deleteByKey: (key) => { capturedKey = key; return Promise.resolve(); },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const scene = makeScene({ cleanup: ["db.deleteByKey('[learner.key]')"] });
|
|
67
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
68
|
+
expect(capturedKey).toBe('learner-123');
|
|
69
|
+
});
|
|
70
|
+
it('interpolates [team.field] tokens from team metadata tags', async () => {
|
|
71
|
+
let capturedLang = '';
|
|
72
|
+
const server = {
|
|
73
|
+
db: {
|
|
74
|
+
deleteByLang: (lang) => { capturedLang = lang; return Promise.resolve(); },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const scene = makeScene({ cleanup: ["db.deleteByLang('[team.lang]')"] });
|
|
78
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
79
|
+
expect(capturedLang).toBe('kan');
|
|
80
|
+
});
|
|
81
|
+
it('interpolates [testStart] with the ISO timestamp', async () => {
|
|
82
|
+
let capturedTs = '';
|
|
83
|
+
const server = {
|
|
84
|
+
db: {
|
|
85
|
+
deleteAfter: (ts) => { capturedTs = ts; return Promise.resolve(); },
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const scene = makeScene({ cleanup: ["db.deleteAfter('[testStart]')"] });
|
|
89
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
90
|
+
expect(capturedTs).toBe(testStart);
|
|
91
|
+
});
|
|
92
|
+
it('logs a warning and skips when no server config', async () => {
|
|
93
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
94
|
+
const scene = makeScene({ cleanup: ["db.delete('x')"] });
|
|
95
|
+
await runCleanup(scene, team, teamMeta, undefined, testStart);
|
|
96
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('no server config'));
|
|
97
|
+
warnSpy.mockRestore();
|
|
98
|
+
});
|
|
99
|
+
it('logs a warning but does not throw when expression errors', async () => {
|
|
100
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
101
|
+
const server = {
|
|
102
|
+
db: {
|
|
103
|
+
badCall: () => { throw new Error('db connection refused'); },
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const scene = makeScene({ cleanup: ['db.badCall()'] });
|
|
107
|
+
await expect(runCleanup(scene, team, teamMeta, server, testStart)).resolves.toBeUndefined();
|
|
108
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('db connection refused'));
|
|
109
|
+
warnSpy.mockRestore();
|
|
110
|
+
});
|
|
111
|
+
it('throws interpolation error and logs warning for unknown role', async () => {
|
|
112
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
113
|
+
const server = { db: {} };
|
|
114
|
+
const scene = makeScene({ cleanup: ["db.fn('[ghost.key]')"] });
|
|
115
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
116
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('ghost'));
|
|
117
|
+
warnSpy.mockRestore();
|
|
118
|
+
});
|
|
119
|
+
it('throws interpolation error and logs warning for unknown team tag', async () => {
|
|
120
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
121
|
+
const server = { db: {} };
|
|
122
|
+
const scene = makeScene({ cleanup: ["db.fn('[team.notexist]')"] });
|
|
123
|
+
await runCleanup(scene, team, teamMeta, server, testStart);
|
|
124
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('notexist'));
|
|
125
|
+
warnSpy.mockRestore();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// runSetup
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
describe('runSetup', () => {
|
|
132
|
+
it('does nothing when setup is empty', async () => {
|
|
133
|
+
const calls = [];
|
|
134
|
+
const server = { db: { update: (t) => { calls.push(t); return Promise.resolve(); } } };
|
|
135
|
+
const scene = makeScene();
|
|
136
|
+
await runSetup(scene, team, teamMeta, server, testStart);
|
|
137
|
+
expect(calls).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
it('evaluates each setup expression in order', async () => {
|
|
140
|
+
const calls = [];
|
|
141
|
+
const server = {
|
|
142
|
+
db: {
|
|
143
|
+
update: (t) => { calls.push(`update:${t}`); return Promise.resolve(); },
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const scene = makeScene({
|
|
147
|
+
setup: ["db.update('step1')", "db.update('step2')"],
|
|
148
|
+
});
|
|
149
|
+
await runSetup(scene, team, teamMeta, server, testStart);
|
|
150
|
+
expect(calls).toEqual(['update:step1', 'update:step2']);
|
|
151
|
+
});
|
|
152
|
+
it('interpolates [role.field] and [team.field] in setup expressions', async () => {
|
|
153
|
+
const captured = [];
|
|
154
|
+
const server = {
|
|
155
|
+
db: {
|
|
156
|
+
seed: (key, lang) => {
|
|
157
|
+
captured.push(`${key}:${lang}`);
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const scene = makeScene({ setup: ["db.seed('[learner.key]', '[team.lang]')"] });
|
|
163
|
+
await runSetup(scene, team, teamMeta, server, testStart);
|
|
164
|
+
expect(captured).toEqual(['learner-123:kan']);
|
|
165
|
+
});
|
|
166
|
+
it('logs a warning but does not throw on error, continues remaining expressions', async () => {
|
|
167
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
168
|
+
const calls = [];
|
|
169
|
+
const server = {
|
|
170
|
+
db: {
|
|
171
|
+
bad: () => { throw new Error('setup failed'); },
|
|
172
|
+
ok: () => { calls.push('ok'); return Promise.resolve(); },
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const scene = makeScene({ setup: ['db.bad()', 'db.ok()'] });
|
|
176
|
+
await runSetup(scene, team, teamMeta, server, testStart);
|
|
177
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('setup failed'));
|
|
178
|
+
expect(calls).toEqual(['ok']);
|
|
179
|
+
warnSpy.mockRestore();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../src/__tests__/runner.test.ts"],"names":[],"mappings":"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"}
|
package/dist/actor.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Page, BrowserContext } from 'playwright';
|
|
2
|
-
import type { ActorConfig, SequentialActorHandle, ActionChain, AssertionResult, TimelineEntry, ScriptWarning, Selector } from './types.js';
|
|
2
|
+
import type { ActorConfig, SequentialActorHandle, ActionChain, AssertionResult, TimelineEntry, ScriptWarning, Selector, PageFactory } from './types.js';
|
|
3
|
+
import type { NavigationMode } from './keyboard.js';
|
|
3
4
|
import { MessageBus } from './message-bus.js';
|
|
4
5
|
/**
|
|
5
6
|
* Registered watcher for conditional handling
|
|
@@ -29,17 +30,28 @@ export declare class SequentialActorHandleImpl implements SequentialActorHandle
|
|
|
29
30
|
private actionTimeout;
|
|
30
31
|
private warnAfter;
|
|
31
32
|
readonly role: string;
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
private _page;
|
|
34
|
+
private _context;
|
|
34
35
|
readonly assertions: AssertionResult[];
|
|
36
|
+
readonly navigationMode: NavigationMode;
|
|
37
|
+
readonly fuzzyFingers: boolean;
|
|
35
38
|
private watchers;
|
|
36
39
|
private warningTriggers;
|
|
40
|
+
private _pageFactory;
|
|
37
41
|
readonly key: string;
|
|
38
42
|
readonly username?: string;
|
|
39
43
|
readonly email?: string;
|
|
40
44
|
readonly password?: string;
|
|
41
45
|
[key: string]: unknown;
|
|
42
|
-
constructor(role: string, config: ActorConfig, page: Page, context: BrowserContext, bus: MessageBus, timeline: TimelineEntry[], warnings: ScriptWarning[], actionTimeout: number, warnAfter: number);
|
|
46
|
+
constructor(role: string, config: ActorConfig, page: Page, context: BrowserContext, bus: MessageBus, timeline: TimelineEntry[], warnings: ScriptWarning[], actionTimeout: number, warnAfter: number, navigationMode?: NavigationMode, fuzzyFingers?: boolean, pageFactory?: PageFactory | null);
|
|
47
|
+
/** Playwright page for this actor */
|
|
48
|
+
get page(): Page;
|
|
49
|
+
/** Playwright browser context for this actor */
|
|
50
|
+
get context(): BrowserContext;
|
|
51
|
+
/** Get the page factory (used by ActionChainImpl for switchDevice) */
|
|
52
|
+
getPageFactory(): PageFactory | null;
|
|
53
|
+
/** Update page and context after a device switch */
|
|
54
|
+
_switchPage(page: Page, context: BrowserContext): void;
|
|
43
55
|
/**
|
|
44
56
|
* Get registered watchers (used by ActionChainImpl)
|
|
45
57
|
*/
|
|
@@ -55,6 +67,10 @@ export declare class SequentialActorHandleImpl implements SequentialActorHandle
|
|
|
55
67
|
clearWatchers(): void;
|
|
56
68
|
private createChain;
|
|
57
69
|
openTo(url: string): ActionChain;
|
|
70
|
+
reload(): ActionChain;
|
|
71
|
+
goBack(): ActionChain;
|
|
72
|
+
goForward(): ActionChain;
|
|
73
|
+
switchDevice(device?: string): ActionChain;
|
|
58
74
|
see(selector: Selector): ActionChain;
|
|
59
75
|
seeInView(selector: Selector): ActionChain;
|
|
60
76
|
notSee(selector: Selector): ActionChain;
|
|
@@ -67,6 +83,7 @@ export declare class SequentialActorHandleImpl implements SequentialActorHandle
|
|
|
67
83
|
wait(ms: number): ActionChain;
|
|
68
84
|
emit(message: string): ActionChain;
|
|
69
85
|
waitFor(message: string): ActionChain;
|
|
86
|
+
pressKey(key: string): ActionChain;
|
|
70
87
|
do(fn: (page: Page) => Promise<void>): ActionChain;
|
|
71
88
|
scrollToBottom(): ActionChain;
|
|
72
89
|
up(selector?: Selector): ActionChain;
|
package/dist/actor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../src/actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAW,MAAM,YAAY,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../src/actor.ts"],"names":[],"mappings":"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"}
|
package/dist/actor.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { tabToElement, pressEnter, pressSpace, clearAndType, keyboardSelectOption, fuzzyFingerClick, fuzzyFingerFill, fuzzyFingerCheck } from './keyboard.js';
|
|
1
2
|
import { resolveSelector } from './selectors.js';
|
|
2
3
|
import { parseDslLines, parseAction, applyDslAction } from './dsl.js';
|
|
4
|
+
import { findDevice } from './devices.js';
|
|
3
5
|
/**
|
|
4
6
|
* Format a Selector for display in logs/errors
|
|
5
7
|
*/
|
|
@@ -19,6 +21,8 @@ class ActionChainImpl {
|
|
|
19
21
|
warnings;
|
|
20
22
|
actionTimeout;
|
|
21
23
|
warnAfter;
|
|
24
|
+
navigationMode;
|
|
25
|
+
fuzzyFingers;
|
|
22
26
|
actions = [];
|
|
23
27
|
// Current scope - starts as page, changes with see()
|
|
24
28
|
currentScope;
|
|
@@ -28,7 +32,7 @@ class ActionChainImpl {
|
|
|
28
32
|
scopeSetUrl = '';
|
|
29
33
|
// Per-entry URLs matching scopeStack (kept in sync)
|
|
30
34
|
scopeStackUrls = [];
|
|
31
|
-
constructor(actor, page, bus, timeline, warnings, actionTimeout, warnAfter) {
|
|
35
|
+
constructor(actor, page, bus, timeline, warnings, actionTimeout, warnAfter, navigationMode = 'pointer', fuzzyFingers = false) {
|
|
32
36
|
this.actor = actor;
|
|
33
37
|
this.page = page;
|
|
34
38
|
this.bus = bus;
|
|
@@ -36,8 +40,23 @@ class ActionChainImpl {
|
|
|
36
40
|
this.warnings = warnings;
|
|
37
41
|
this.actionTimeout = actionTimeout;
|
|
38
42
|
this.warnAfter = warnAfter;
|
|
43
|
+
this.navigationMode = navigationMode;
|
|
44
|
+
this.fuzzyFingers = fuzzyFingers;
|
|
39
45
|
this.currentScope = page;
|
|
40
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether this chain uses keyboard navigation.
|
|
49
|
+
*/
|
|
50
|
+
get isKeyboard() {
|
|
51
|
+
return this.navigationMode === 'keyboard';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether fuzzy-finger touch behavior is enabled for pointer mode.
|
|
55
|
+
* Fuzzy-finger simulates imprecise human touch: miss → pause → correct click.
|
|
56
|
+
*/
|
|
57
|
+
get useFuzzyFingers() {
|
|
58
|
+
return this.navigationMode === 'pointer' && this.fuzzyFingers;
|
|
59
|
+
}
|
|
41
60
|
addAction(name, target, execute) {
|
|
42
61
|
this.actions.push({ name, target, execute });
|
|
43
62
|
return this;
|
|
@@ -120,6 +139,53 @@ class ActionChainImpl {
|
|
|
120
139
|
this.scopeStackUrls = [];
|
|
121
140
|
});
|
|
122
141
|
}
|
|
142
|
+
reload() {
|
|
143
|
+
return this.addAction('reload', undefined, async () => {
|
|
144
|
+
await this.page.reload({ timeout: this.actionTimeout });
|
|
145
|
+
this.currentScope = this.page;
|
|
146
|
+
this.scopeStack = [];
|
|
147
|
+
this.scopeSetUrl = '';
|
|
148
|
+
this.scopeStackUrls = [];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
goBack() {
|
|
152
|
+
return this.addAction('goBack', undefined, async () => {
|
|
153
|
+
await this.page.goBack({ timeout: this.actionTimeout });
|
|
154
|
+
this.currentScope = this.page;
|
|
155
|
+
this.scopeStack = [];
|
|
156
|
+
this.scopeSetUrl = '';
|
|
157
|
+
this.scopeStackUrls = [];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
goForward() {
|
|
161
|
+
return this.addAction('goForward', undefined, async () => {
|
|
162
|
+
await this.page.goForward({ timeout: this.actionTimeout });
|
|
163
|
+
this.currentScope = this.page;
|
|
164
|
+
this.scopeStack = [];
|
|
165
|
+
this.scopeSetUrl = '';
|
|
166
|
+
this.scopeStackUrls = [];
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
switchDevice(device) {
|
|
170
|
+
return this.addAction('switchDevice', device ?? '(next)', async () => {
|
|
171
|
+
const factory = this.actor.getPageFactory();
|
|
172
|
+
if (!factory) {
|
|
173
|
+
throw new Error('switchDevice requires the scene runner (page factory not available)');
|
|
174
|
+
}
|
|
175
|
+
const profile = device ? findDevice(device) : null;
|
|
176
|
+
// Factory closes old context, creates new one with assertion wiring
|
|
177
|
+
const { page, context } = await factory(profile);
|
|
178
|
+
// Update actor's backing references
|
|
179
|
+
this.actor._switchPage(page, context);
|
|
180
|
+
// Update chain's local page reference
|
|
181
|
+
this.page = page;
|
|
182
|
+
// Reset scope to new page root
|
|
183
|
+
this.currentScope = this.page;
|
|
184
|
+
this.scopeStack = [];
|
|
185
|
+
this.scopeSetUrl = '';
|
|
186
|
+
this.scopeStackUrls = [];
|
|
187
|
+
});
|
|
188
|
+
}
|
|
123
189
|
see(selector) {
|
|
124
190
|
const target = formatSelector(selector);
|
|
125
191
|
return this.addAction('see', target, async () => {
|
|
@@ -177,31 +243,85 @@ class ActionChainImpl {
|
|
|
177
243
|
if (scope === this.page) {
|
|
178
244
|
throw new Error('click with no selector requires a scope (use see() first)');
|
|
179
245
|
}
|
|
180
|
-
|
|
246
|
+
if (this.isKeyboard) {
|
|
247
|
+
await tabToElement(this.page, scope, { timeout: this.actionTimeout });
|
|
248
|
+
await pressEnter(this.page);
|
|
249
|
+
}
|
|
250
|
+
else if (this.useFuzzyFingers) {
|
|
251
|
+
await fuzzyFingerClick(this.page, scope, this.actionTimeout, '(scope)');
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
await scope.click({ timeout: this.actionTimeout });
|
|
255
|
+
}
|
|
181
256
|
});
|
|
182
257
|
}
|
|
183
258
|
const target = formatSelector(selector);
|
|
184
259
|
return this.addAction('click', target, async () => {
|
|
185
|
-
|
|
260
|
+
const locator = resolveSelector(this.getScope(), selector);
|
|
261
|
+
if (this.isKeyboard) {
|
|
262
|
+
await locator.waitFor({ state: 'visible', timeout: this.actionTimeout });
|
|
263
|
+
await tabToElement(this.page, locator, { timeout: this.actionTimeout });
|
|
264
|
+
await pressEnter(this.page);
|
|
265
|
+
}
|
|
266
|
+
else if (this.useFuzzyFingers) {
|
|
267
|
+
await fuzzyFingerClick(this.page, locator, this.actionTimeout, selector);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
await locator.click({ timeout: this.actionTimeout });
|
|
271
|
+
}
|
|
186
272
|
});
|
|
187
273
|
}
|
|
188
274
|
typeInto(selector, value) {
|
|
189
275
|
const target = `${formatSelector(selector)}=${value}`;
|
|
190
276
|
return this.addAction('typeInto', target, async () => {
|
|
191
|
-
|
|
277
|
+
const locator = resolveSelector(this.getScope(), selector);
|
|
278
|
+
if (this.isKeyboard) {
|
|
279
|
+
await locator.waitFor({ state: 'visible', timeout: this.actionTimeout });
|
|
280
|
+
await tabToElement(this.page, locator, { timeout: this.actionTimeout });
|
|
281
|
+
await clearAndType(this.page, value);
|
|
282
|
+
}
|
|
283
|
+
else if (this.useFuzzyFingers) {
|
|
284
|
+
await fuzzyFingerFill(this.page, locator, value, this.actionTimeout, selector);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
await locator.fill(value, { timeout: this.actionTimeout });
|
|
288
|
+
}
|
|
192
289
|
// typeInto stays in current scope
|
|
193
290
|
});
|
|
194
291
|
}
|
|
195
292
|
check(selector) {
|
|
196
293
|
const target = formatSelector(selector);
|
|
197
294
|
return this.addAction('check', target, async () => {
|
|
198
|
-
|
|
295
|
+
const locator = resolveSelector(this.getScope(), selector);
|
|
296
|
+
if (this.isKeyboard) {
|
|
297
|
+
await locator.waitFor({ state: 'visible', timeout: this.actionTimeout });
|
|
298
|
+
await tabToElement(this.page, locator, { timeout: this.actionTimeout });
|
|
299
|
+
await pressSpace(this.page);
|
|
300
|
+
}
|
|
301
|
+
else if (this.useFuzzyFingers) {
|
|
302
|
+
await fuzzyFingerCheck(this.page, locator, this.actionTimeout, selector);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
await locator.check({ timeout: this.actionTimeout });
|
|
306
|
+
}
|
|
199
307
|
});
|
|
200
308
|
}
|
|
201
309
|
select(selector, value) {
|
|
202
310
|
const target = `${formatSelector(selector)}=${value}`;
|
|
203
311
|
return this.addAction('select', target, async () => {
|
|
204
|
-
|
|
312
|
+
const locator = resolveSelector(this.getScope(), selector);
|
|
313
|
+
if (this.isKeyboard) {
|
|
314
|
+
await locator.waitFor({ state: 'visible', timeout: this.actionTimeout });
|
|
315
|
+
await tabToElement(this.page, locator, { timeout: this.actionTimeout });
|
|
316
|
+
await keyboardSelectOption(this.page, locator, value);
|
|
317
|
+
}
|
|
318
|
+
else if (this.useFuzzyFingers) {
|
|
319
|
+
await fuzzyFingerClick(this.page, locator, this.actionTimeout, selector);
|
|
320
|
+
await locator.selectOption(value, { timeout: this.actionTimeout });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
await locator.selectOption(value, { timeout: this.actionTimeout });
|
|
324
|
+
}
|
|
205
325
|
});
|
|
206
326
|
}
|
|
207
327
|
wait(ms) {
|
|
@@ -219,6 +339,11 @@ class ActionChainImpl {
|
|
|
219
339
|
await this.bus.waitFor(message);
|
|
220
340
|
});
|
|
221
341
|
}
|
|
342
|
+
pressKey(key) {
|
|
343
|
+
return this.addAction('pressKey', key, async () => {
|
|
344
|
+
await this.page.keyboard.press(key);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
222
347
|
do(fn) {
|
|
223
348
|
return this.addAction('do', 'custom', async () => {
|
|
224
349
|
await fn(this.page);
|
|
@@ -442,19 +567,23 @@ export class SequentialActorHandleImpl {
|
|
|
442
567
|
actionTimeout;
|
|
443
568
|
warnAfter;
|
|
444
569
|
role;
|
|
445
|
-
|
|
446
|
-
|
|
570
|
+
_page;
|
|
571
|
+
_context;
|
|
447
572
|
assertions = [];
|
|
573
|
+
navigationMode;
|
|
574
|
+
fuzzyFingers;
|
|
448
575
|
// Registered watchers for conditional handling
|
|
449
576
|
watchers = [];
|
|
450
577
|
// Registered warning triggers
|
|
451
578
|
warningTriggers = [];
|
|
579
|
+
// Page factory for switchDevice support
|
|
580
|
+
_pageFactory;
|
|
452
581
|
// Forward config properties
|
|
453
582
|
key;
|
|
454
583
|
username;
|
|
455
584
|
email;
|
|
456
585
|
password;
|
|
457
|
-
constructor(role, config, page, context, bus, timeline, warnings, actionTimeout, warnAfter) {
|
|
586
|
+
constructor(role, config, page, context, bus, timeline, warnings, actionTimeout, warnAfter, navigationMode = 'pointer', fuzzyFingers = false, pageFactory) {
|
|
458
587
|
this.config = config;
|
|
459
588
|
this.bus = bus;
|
|
460
589
|
this.timeline = timeline;
|
|
@@ -462,9 +591,12 @@ export class SequentialActorHandleImpl {
|
|
|
462
591
|
this.actionTimeout = actionTimeout;
|
|
463
592
|
this.warnAfter = warnAfter;
|
|
464
593
|
this.role = role;
|
|
465
|
-
this.
|
|
466
|
-
this.
|
|
594
|
+
this._page = page;
|
|
595
|
+
this._context = context;
|
|
596
|
+
this._pageFactory = pageFactory ?? null;
|
|
467
597
|
this.key = config.key;
|
|
598
|
+
this.navigationMode = navigationMode;
|
|
599
|
+
this.fuzzyFingers = fuzzyFingers;
|
|
468
600
|
// Copy all config properties to this instance
|
|
469
601
|
for (const [k, value] of Object.entries(config)) {
|
|
470
602
|
if (!(k in this)) {
|
|
@@ -475,6 +607,23 @@ export class SequentialActorHandleImpl {
|
|
|
475
607
|
}
|
|
476
608
|
}
|
|
477
609
|
}
|
|
610
|
+
/** Playwright page for this actor */
|
|
611
|
+
get page() {
|
|
612
|
+
return this._page;
|
|
613
|
+
}
|
|
614
|
+
/** Playwright browser context for this actor */
|
|
615
|
+
get context() {
|
|
616
|
+
return this._context;
|
|
617
|
+
}
|
|
618
|
+
/** Get the page factory (used by ActionChainImpl for switchDevice) */
|
|
619
|
+
getPageFactory() {
|
|
620
|
+
return this._pageFactory;
|
|
621
|
+
}
|
|
622
|
+
/** Update page and context after a device switch */
|
|
623
|
+
_switchPage(page, context) {
|
|
624
|
+
this._page = page;
|
|
625
|
+
this._context = context;
|
|
626
|
+
}
|
|
478
627
|
/**
|
|
479
628
|
* Get registered watchers (used by ActionChainImpl)
|
|
480
629
|
*/
|
|
@@ -495,11 +644,23 @@ export class SequentialActorHandleImpl {
|
|
|
495
644
|
this.watchers = [];
|
|
496
645
|
}
|
|
497
646
|
createChain() {
|
|
498
|
-
return new ActionChainImpl(this, this.page, this.bus, this.timeline, this.warnings, this.actionTimeout, this.warnAfter);
|
|
647
|
+
return new ActionChainImpl(this, this.page, this.bus, this.timeline, this.warnings, this.actionTimeout, this.warnAfter, this.navigationMode, this.fuzzyFingers);
|
|
499
648
|
}
|
|
500
649
|
openTo(url) {
|
|
501
650
|
return this.createChain().openTo(url);
|
|
502
651
|
}
|
|
652
|
+
reload() {
|
|
653
|
+
return this.createChain().reload();
|
|
654
|
+
}
|
|
655
|
+
goBack() {
|
|
656
|
+
return this.createChain().goBack();
|
|
657
|
+
}
|
|
658
|
+
goForward() {
|
|
659
|
+
return this.createChain().goForward();
|
|
660
|
+
}
|
|
661
|
+
switchDevice(device) {
|
|
662
|
+
return this.createChain().switchDevice(device);
|
|
663
|
+
}
|
|
503
664
|
see(selector) {
|
|
504
665
|
return this.createChain().see(selector);
|
|
505
666
|
}
|
|
@@ -536,6 +697,9 @@ export class SequentialActorHandleImpl {
|
|
|
536
697
|
waitFor(message) {
|
|
537
698
|
return this.createChain().waitFor(message);
|
|
538
699
|
}
|
|
700
|
+
pressKey(key) {
|
|
701
|
+
return this.createChain().pressKey(key);
|
|
702
|
+
}
|
|
539
703
|
do(fn) {
|
|
540
704
|
return this.createChain().do(fn);
|
|
541
705
|
}
|