@rehearsal-labs/retest 0.1.0 → 0.2.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/README.md +6 -0
- package/dist/agent/session.d.ts +21 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +73 -11
- package/dist/api/app-page.d.ts +39 -3
- package/dist/api/app-page.d.ts.map +1 -1
- package/dist/api/app-page.js +108 -0
- package/dist/api/dialog.d.ts +38 -0
- package/dist/api/dialog.d.ts.map +1 -0
- package/dist/api/dialog.js +78 -0
- package/dist/api/drag.d.ts +32 -0
- package/dist/api/drag.d.ts.map +1 -0
- package/dist/api/drag.js +26 -0
- package/dist/api/files.d.ts +49 -0
- package/dist/api/files.d.ts.map +1 -0
- package/dist/api/files.js +168 -0
- package/dist/api/inbox.d.ts +97 -0
- package/dist/api/inbox.d.ts.map +1 -0
- package/dist/api/inbox.js +191 -0
- package/dist/api/locator-recipes.d.ts +1 -1
- package/dist/api/locator-recipes.d.ts.map +1 -1
- package/dist/api/locator-recipes.js +4 -3
- package/dist/api/page.d.ts +214 -4
- package/dist/api/page.d.ts.map +1 -1
- package/dist/api/route.d.ts +21 -0
- package/dist/api/route.d.ts.map +1 -0
- package/dist/api/route.js +99 -0
- package/dist/api/test-run.d.ts +127 -1
- package/dist/api/test-run.d.ts.map +1 -1
- package/dist/api/test-run.js +293 -0
- package/dist/assertions/expect.d.ts +91 -2
- package/dist/assertions/expect.d.ts.map +1 -1
- package/dist/assertions/expect.js +98 -0
- package/dist/assertions/look-until.d.ts +5 -0
- package/dist/assertions/look-until.d.ts.map +1 -1
- package/dist/assertions/look-until.js +1 -1
- package/dist/assertions/poll-layout.d.ts +24 -0
- package/dist/assertions/poll-layout.d.ts.map +1 -0
- package/dist/assertions/poll-layout.js +63 -0
- package/dist/assertions/screenshot.d.ts +36 -0
- package/dist/assertions/screenshot.d.ts.map +1 -0
- package/dist/assertions/screenshot.js +125 -0
- package/dist/browser/accessibility.d.ts.map +1 -1
- package/dist/browser/accessibility.js +22 -1
- package/dist/browser/actionability.d.ts +9 -6
- package/dist/browser/actionability.d.ts.map +1 -1
- package/dist/browser/actionability.js +17 -8
- package/dist/browser/browser.d.ts.map +1 -1
- package/dist/browser/browser.js +15 -4
- package/dist/browser/cdp/connection.d.ts +6 -0
- package/dist/browser/cdp/connection.d.ts.map +1 -1
- package/dist/browser/cdp/connection.js +8 -0
- package/dist/browser/checked-state.d.ts +2 -2
- package/dist/browser/checked-state.d.ts.map +1 -1
- package/dist/browser/chromium-process.d.ts +9 -2
- package/dist/browser/chromium-process.d.ts.map +1 -1
- package/dist/browser/chromium-process.js +17 -4
- package/dist/browser/contract.d.ts +119 -5
- package/dist/browser/contract.d.ts.map +1 -1
- package/dist/browser/dialogs.d.ts +157 -0
- package/dist/browser/dialogs.d.ts.map +1 -0
- package/dist/browser/dialogs.js +271 -0
- package/dist/browser/downloads.d.ts +71 -0
- package/dist/browser/downloads.d.ts.map +1 -0
- package/dist/browser/downloads.js +253 -0
- package/dist/browser/drag.d.ts +50 -0
- package/dist/browser/drag.d.ts.map +1 -0
- package/dist/browser/drag.js +584 -0
- package/dist/browser/electron-window-watch.d.ts +32 -0
- package/dist/browser/electron-window-watch.d.ts.map +1 -0
- package/dist/browser/electron-window-watch.js +93 -0
- package/dist/browser/electron.d.ts +13 -2
- package/dist/browser/electron.d.ts.map +1 -1
- package/dist/browser/electron.js +53 -8
- package/dist/browser/element-queries.d.ts +51 -15
- package/dist/browser/element-queries.d.ts.map +1 -1
- package/dist/browser/element-queries.js +92 -13
- package/dist/browser/emulation.d.ts.map +1 -1
- package/dist/browser/emulation.js +7 -5
- package/dist/browser/file-chooser.d.ts +75 -0
- package/dist/browser/file-chooser.d.ts.map +1 -0
- package/dist/browser/file-chooser.js +449 -0
- package/dist/browser/firefox/input.d.ts +5 -0
- package/dist/browser/firefox/input.d.ts.map +1 -1
- package/dist/browser/firefox/input.js +6 -3
- package/dist/browser/firefox/page.d.ts +11 -1
- package/dist/browser/firefox/page.d.ts.map +1 -1
- package/dist/browser/firefox/page.js +66 -7
- package/dist/browser/frames.d.ts +205 -0
- package/dist/browser/frames.d.ts.map +1 -0
- package/dist/browser/frames.js +657 -0
- package/dist/browser/input-guard.d.ts +18 -5
- package/dist/browser/input-guard.d.ts.map +1 -1
- package/dist/browser/input-guard.js +65 -6
- package/dist/browser/isolated-world.d.ts +8 -1
- package/dist/browser/isolated-world.d.ts.map +1 -1
- package/dist/browser/layout.d.ts +120 -0
- package/dist/browser/layout.d.ts.map +1 -0
- package/dist/browser/layout.js +213 -0
- package/dist/browser/locate.d.ts +10 -0
- package/dist/browser/locate.d.ts.map +1 -1
- package/dist/browser/locate.js +24 -6
- package/dist/browser/origin-document.d.ts +4 -0
- package/dist/browser/origin-document.d.ts.map +1 -1
- package/dist/browser/origin-document.js +9 -1
- package/dist/browser/page-scripts.d.ts +48 -0
- package/dist/browser/page-scripts.d.ts.map +1 -1
- package/dist/browser/page-scripts.js +515 -39
- package/dist/browser/page.d.ts +60 -1
- package/dist/browser/page.d.ts.map +1 -1
- package/dist/browser/page.js +275 -32
- package/dist/browser/popups.d.ts +166 -0
- package/dist/browser/popups.d.ts.map +1 -0
- package/dist/browser/popups.js +317 -0
- package/dist/browser/routes.d.ts +97 -0
- package/dist/browser/routes.d.ts.map +1 -0
- package/dist/browser/routes.js +226 -0
- package/dist/browser/screenshot.d.ts +44 -0
- package/dist/browser/screenshot.d.ts.map +1 -0
- package/dist/browser/screenshot.js +51 -0
- package/dist/browser/shadow-candidates.d.ts +20 -0
- package/dist/browser/shadow-candidates.d.ts.map +1 -0
- package/dist/browser/shadow-candidates.js +77 -0
- package/dist/browser/webkit/page.d.ts +11 -1
- package/dist/browser/webkit/page.d.ts.map +1 -1
- package/dist/browser/webkit/page.js +53 -2
- package/dist/browser/webkit/relay.d.ts.map +1 -1
- package/dist/browser/webkit/relay.js +5 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +2 -0
- package/dist/cli/inspect/test-timeline.d.ts.map +1 -1
- package/dist/cli/inspect/test-timeline.js +38 -4
- package/dist/config/loaded.d.ts +12 -2
- package/dist/config/loaded.d.ts.map +1 -1
- package/dist/config/read-inboxes.d.ts +10 -0
- package/dist/config/read-inboxes.d.ts.map +1 -0
- package/dist/config/read-inboxes.js +78 -0
- package/dist/config/register.d.ts +5 -1
- package/dist/config/register.d.ts.map +1 -1
- package/dist/config/types.d.ts +36 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/validate.d.ts.map +1 -1
- package/dist/config/validate.js +4 -1
- package/dist/diagnostics/artifact.d.ts.map +1 -1
- package/dist/diagnostics/artifact.js +10 -2
- package/dist/diagnostics/attempt.d.ts +5 -0
- package/dist/diagnostics/attempt.d.ts.map +1 -1
- package/dist/diagnostics/attempt.js +10 -0
- package/dist/diagnostics/chromium-collector.d.ts +9 -1
- package/dist/diagnostics/chromium-collector.d.ts.map +1 -1
- package/dist/diagnostics/chromium-collector.js +19 -4
- package/dist/diagnostics/observations.d.ts +8 -0
- package/dist/diagnostics/observations.d.ts.map +1 -1
- package/dist/diagnostics/session-capture.d.ts.map +1 -1
- package/dist/diagnostics/session-capture.js +8 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/media/policy.d.ts +2 -2
- package/dist/media/policy.d.ts.map +1 -1
- package/dist/media/policy.js +1 -1
- package/dist/playwright/not-yet.d.ts.map +1 -1
- package/dist/playwright/not-yet.js +5 -2
- package/dist/protocol/commands.d.ts +122 -13
- package/dist/protocol/commands.d.ts.map +1 -1
- package/dist/protocol/commands.js +85 -6
- package/dist/protocol/diagnostics.d.ts +10 -0
- package/dist/protocol/diagnostics.d.ts.map +1 -1
- package/dist/protocol/diagnostics.js +5 -0
- package/dist/protocol/dialogs.d.ts +104 -0
- package/dist/protocol/dialogs.d.ts.map +1 -0
- package/dist/protocol/dialogs.js +101 -0
- package/dist/protocol/drag.d.ts +45 -0
- package/dist/protocol/drag.d.ts.map +1 -0
- package/dist/protocol/drag.js +31 -0
- package/dist/protocol/events.d.ts +153 -1
- package/dist/protocol/events.d.ts.map +1 -1
- package/dist/protocol/events.js +97 -0
- package/dist/protocol/files.d.ts +83 -0
- package/dist/protocol/files.d.ts.map +1 -0
- package/dist/protocol/files.js +68 -0
- package/dist/protocol/inbox.d.ts +87 -0
- package/dist/protocol/inbox.d.ts.map +1 -0
- package/dist/protocol/inbox.js +51 -0
- package/dist/protocol/index.d.ts +4 -1
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/layout-checks.d.ts +83 -0
- package/dist/protocol/layout-checks.d.ts.map +1 -0
- package/dist/protocol/layout-checks.js +214 -0
- package/dist/protocol/layout.d.ts +135 -0
- package/dist/protocol/layout.d.ts.map +1 -0
- package/dist/protocol/layout.js +124 -0
- package/dist/protocol/locator-checks.d.ts +19 -2
- package/dist/protocol/locator-checks.d.ts.map +1 -1
- package/dist/protocol/locator-checks.js +23 -1
- package/dist/protocol/locator.d.ts +55 -7
- package/dist/protocol/locator.d.ts.map +1 -1
- package/dist/protocol/locator.js +99 -18
- package/dist/protocol/messages.d.ts +95 -0
- package/dist/protocol/messages.d.ts.map +1 -1
- package/dist/protocol/messages.js +63 -0
- package/dist/protocol/popups.d.ts +43 -0
- package/dist/protocol/popups.d.ts.map +1 -0
- package/dist/protocol/popups.js +27 -0
- package/dist/protocol/result.d.ts +5 -0
- package/dist/protocol/result.d.ts.map +1 -1
- package/dist/protocol/result.js +1 -0
- package/dist/protocol/routes.d.ts +136 -0
- package/dist/protocol/routes.d.ts.map +1 -0
- package/dist/protocol/routes.js +191 -0
- package/dist/protocol/screenshot.d.ts +219 -0
- package/dist/protocol/screenshot.d.ts.map +1 -0
- package/dist/protocol/screenshot.js +335 -0
- package/dist/reporters/actions.d.ts +6 -4
- package/dist/reporters/actions.d.ts.map +1 -1
- package/dist/reporters/actions.js +24 -6
- package/dist/reporters/agent.d.ts.map +1 -1
- package/dist/reporters/agent.js +4 -2
- package/dist/reporters/dialogs-popups.d.ts +22 -0
- package/dist/reporters/dialogs-popups.d.ts.map +1 -0
- package/dist/reporters/dialogs-popups.js +30 -0
- package/dist/reporters/failure-card.d.ts +10 -1
- package/dist/reporters/failure-card.d.ts.map +1 -1
- package/dist/reporters/failure-card.js +5 -1
- package/dist/reporters/format.d.ts +33 -0
- package/dist/reporters/format.d.ts.map +1 -1
- package/dist/reporters/format.js +42 -0
- package/dist/reporters/html/comparison-view.d.ts +12 -0
- package/dist/reporters/html/comparison-view.d.ts.map +1 -0
- package/dist/reporters/html/comparison-view.js +21 -0
- package/dist/reporters/html/diagnostics-view.js +8 -0
- package/dist/reporters/html/downloads-view.d.ts +10 -0
- package/dist/reporters/html/downloads-view.d.ts.map +1 -0
- package/dist/reporters/html/downloads-view.js +29 -0
- package/dist/reporters/html/style.d.ts.map +1 -1
- package/dist/reporters/html/style.js +3 -0
- package/dist/reporters/html/summary-view.d.ts.map +1 -1
- package/dist/reporters/html/summary-view.js +3 -2
- package/dist/reporters/html/test-view.d.ts.map +1 -1
- package/dist/reporters/html/test-view.js +6 -3
- package/dist/reporters/html/timeline-view.d.ts.map +1 -1
- package/dist/reporters/html/timeline-view.js +26 -2
- package/dist/reporters/human-card.js +10 -0
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +3 -1
- package/dist/reporters/routes.d.ts +9 -0
- package/dist/reporters/routes.d.ts.map +1 -0
- package/dist/reporters/routes.js +17 -0
- package/dist/runner/child-program.js +10 -0
- package/dist/runner/contract.d.ts +5 -0
- package/dist/runner/contract.d.ts.map +1 -1
- package/dist/runner/dialogs.d.ts +71 -0
- package/dist/runner/dialogs.d.ts.map +1 -0
- package/dist/runner/dialogs.js +189 -0
- package/dist/runner/files.d.ts +79 -0
- package/dist/runner/files.d.ts.map +1 -0
- package/dist/runner/files.js +312 -0
- package/dist/runner/inbox-links.d.ts +19 -0
- package/dist/runner/inbox-links.d.ts.map +1 -0
- package/dist/runner/inbox-links.js +63 -0
- package/dist/runner/inbox.d.ts +87 -0
- package/dist/runner/inbox.d.ts.map +1 -0
- package/dist/runner/inbox.js +340 -0
- package/dist/runner/index.d.ts +2 -1
- package/dist/runner/index.d.ts.map +1 -1
- package/dist/runner/native-pool.d.ts.map +1 -1
- package/dist/runner/native-pool.js +2 -0
- package/dist/runner/observations.d.ts +15 -2
- package/dist/runner/observations.d.ts.map +1 -1
- package/dist/runner/observations.js +109 -6
- package/dist/runner/popups.d.ts +82 -0
- package/dist/runner/popups.d.ts.map +1 -0
- package/dist/runner/popups.js +236 -0
- package/dist/runner/redactor.d.ts.map +1 -1
- package/dist/runner/redactor.js +10 -6
- package/dist/runner/routes.d.ts +23 -0
- package/dist/runner/routes.d.ts.map +1 -0
- package/dist/runner/routes.js +128 -0
- package/dist/runner/run-media.d.ts +6 -0
- package/dist/runner/run-media.d.ts.map +1 -1
- package/dist/runner/run-media.js +21 -0
- package/dist/runner/run-session.d.ts.map +1 -1
- package/dist/runner/run-session.js +93 -8
- package/dist/runner/running-test.d.ts +35 -0
- package/dist/runner/running-test.d.ts.map +1 -1
- package/dist/runner/running-test.js +333 -22
- package/dist/runner/screenshot-baselines.d.ts +159 -0
- package/dist/runner/screenshot-baselines.d.ts.map +1 -0
- package/dist/runner/screenshot-baselines.js +357 -0
- package/dist/runner/secrets.d.ts +13 -0
- package/dist/runner/secrets.d.ts.map +1 -1
- package/dist/runner/secrets.js +6 -4
- package/dist/runner/test-pages.d.ts +3 -1
- package/dist/runner/test-pages.d.ts.map +1 -1
- package/dist/runner/test-pages.js +4 -3
- package/dist/schemas/event-v1.schema.json +118873 -6311
- package/dist/schemas/result-v1.schema.json +4 -0
- package/dist/shared/image-diff.d.ts +36 -0
- package/dist/shared/image-diff.d.ts.map +1 -0
- package/dist/shared/image-diff.js +242 -0
- package/dist/shared/metadata-process.d.ts +4 -1
- package/dist/shared/metadata-process.d.ts.map +1 -1
- package/dist/shared/metadata-process.js +51 -9
- package/dist/shared/png.d.ts +35 -0
- package/dist/shared/png.d.ts.map +1 -0
- package/dist/shared/png.js +251 -0
- package/dist/shared/process-ownership.d.ts +15 -4
- package/dist/shared/process-ownership.d.ts.map +1 -1
- package/dist/shared/process-ownership.js +123 -34
- package/dist/store/artifacts.d.ts +23 -2
- package/dist/store/artifacts.d.ts.map +1 -1
- package/dist/store/artifacts.js +40 -1
- package/dist/store/rebuild-result.d.ts.map +1 -1
- package/dist/store/rebuild-result.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { waitUntilDeadline } from "../assertions/wait-before-read.js";
|
|
2
|
+
import { isDialogType } from "../protocol/dialogs.js";
|
|
3
|
+
import { s } from "../protocol/schema.js";
|
|
4
|
+
import { readProtocol, request, sendOptions } from "./cdp-results.js";
|
|
5
|
+
const attachedSchema = s.object({
|
|
6
|
+
sessionId: s.string(),
|
|
7
|
+
targetInfo: s.object({ targetId: s.string(), type: s.string(), subtype: s.optional(s.string()), browserContextId: s.optional(s.string()) }),
|
|
8
|
+
waitingForDebugger: s.boolean(),
|
|
9
|
+
});
|
|
10
|
+
// Every part but the kind is read leniently, as a page reads it: a dialog Retest could not read would hold the app.
|
|
11
|
+
const openingSchema = s.object({ type: s.string(), message: s.optional(s.string()) });
|
|
12
|
+
const ignore = () => { };
|
|
13
|
+
/**
|
|
14
|
+
* Attaches an Electron app's first window before its document can show a dialog Retest would not hear, and answers
|
|
15
|
+
* the dialogs it shows until its page takes them over with `handOver`. Every other page the app opens is let run at
|
|
16
|
+
* once and let go, as a test reaches only the first window.
|
|
17
|
+
*/
|
|
18
|
+
export class ElectronWindowWatch {
|
|
19
|
+
#connection;
|
|
20
|
+
#window = Promise.withResolvers();
|
|
21
|
+
#answered = [];
|
|
22
|
+
#taken = false;
|
|
23
|
+
#stopAnswering;
|
|
24
|
+
constructor(connection) {
|
|
25
|
+
this.#connection = connection;
|
|
26
|
+
connection.on('Target.attachedToTarget', (params) => this.#attached(params));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Asks the app to attach every page it has and hold every new one. Call it before any other command, so it is the
|
|
30
|
+
* first the app reads; it settles once the app answered, within the deadline.
|
|
31
|
+
*/
|
|
32
|
+
start(deadline) {
|
|
33
|
+
const params = { autoAttach: true, waitForDebuggerOnStart: true, flatten: true, filter: [{ type: 'page' }] };
|
|
34
|
+
return request(this.#connection, 'Target.setAutoAttach', params, s.object({}), sendOptions(deadline)).then(ignore);
|
|
35
|
+
}
|
|
36
|
+
/** The first window, once attached; undefined when the deadline passes or the app goes first. */
|
|
37
|
+
async first(deadline) {
|
|
38
|
+
const timer = new AbortController();
|
|
39
|
+
const ended = Promise.withResolvers();
|
|
40
|
+
const stopListening = this.#connection.onDisconnect(() => ended.resolve(undefined));
|
|
41
|
+
if (this.#connection.closeReason !== undefined)
|
|
42
|
+
ended.resolve(undefined);
|
|
43
|
+
const expired = waitUntilDeadline(deadline, timer.signal).then(() => undefined, () => undefined);
|
|
44
|
+
try {
|
|
45
|
+
return await Promise.race([this.#window.promise, ended.promise, expired]);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
timer.abort();
|
|
49
|
+
stopListening();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Stops answering the first window's dialogs, and gives every dialog answered so far, in order. The page that takes
|
|
54
|
+
* the window over answers and tells every dialog from then on.
|
|
55
|
+
*/
|
|
56
|
+
handOver() {
|
|
57
|
+
this.#stopAnswering?.();
|
|
58
|
+
this.#stopAnswering = undefined;
|
|
59
|
+
return [...this.#answered];
|
|
60
|
+
}
|
|
61
|
+
// The app's first plain page is the test's. A page held as it opened is let run and let go; a page attached without
|
|
62
|
+
// being held, as one open before the watch began, is left as it is.
|
|
63
|
+
#attached(params) {
|
|
64
|
+
const { sessionId, targetInfo, waitingForDebugger } = readProtocol(attachedSchema, params, { method: 'Target.attachedToTarget' });
|
|
65
|
+
if (!this.#taken && targetInfo.type === 'page' && targetInfo.subtype === undefined) {
|
|
66
|
+
this.#take(sessionId, { targetId: targetInfo.targetId, browserContextId: targetInfo.browserContextId ?? '' }, waitingForDebugger);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!waitingForDebugger)
|
|
70
|
+
return;
|
|
71
|
+
this.#connection.adopt(sessionId).send('Runtime.runIfWaitingForDebugger').catch(ignore);
|
|
72
|
+
this.#connection.send('Target.detachFromTarget', { sessionId }).catch(ignore);
|
|
73
|
+
}
|
|
74
|
+
// The session is taken on while its attach is handled, so none of its events comes first. The Page domain goes on
|
|
75
|
+
// before the window may run, and a held window is let run only after it.
|
|
76
|
+
#take(sessionId, target, held) {
|
|
77
|
+
this.#taken = true;
|
|
78
|
+
const session = this.#connection.adopt(sessionId);
|
|
79
|
+
this.#stopAnswering = session.on('Page.javascriptDialogOpening', (params) => this.#answer(session, params));
|
|
80
|
+
session.send('Page.enable').catch(ignore);
|
|
81
|
+
if (held)
|
|
82
|
+
session.send('Runtime.runIfWaitingForDebugger').catch(ignore);
|
|
83
|
+
this.#window.resolve({ session, ...target });
|
|
84
|
+
}
|
|
85
|
+
// Dismissed, or accepted for a beforeunload, as a page answers a dialog nothing waited for.
|
|
86
|
+
#answer(session, params) {
|
|
87
|
+
const { type, message = '' } = readProtocol(openingSchema, params, { method: 'Page.javascriptDialogOpening', sessionId: session.id });
|
|
88
|
+
const kind = isDialogType(type) ? type : 'alert';
|
|
89
|
+
const accept = kind === 'beforeunload';
|
|
90
|
+
this.#answered.push({ type: kind, message, answered: accept ? 'accepted' : 'dismissed', during: false });
|
|
91
|
+
session.send('Page.handleJavaScriptDialog', { accept }).catch(ignore);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FrameSource } from '../media/capture.ts';
|
|
2
2
|
import type { RecordIdentity } from '../protocol/identity.ts';
|
|
3
|
-
import type { SessionIdentity } from './contract.ts';
|
|
3
|
+
import type { RequestResult, SessionIdentity } from './contract.ts';
|
|
4
|
+
import type { DialogArming, DialogReply, UnwaitedDialog } from './dialogs.ts';
|
|
5
|
+
import type { PopupArming } from './popups.ts';
|
|
4
6
|
import type { CdpSession } from './cdp/session.ts';
|
|
5
7
|
import type { PipeStreams, Transport } from './cdp/transport.ts';
|
|
6
|
-
import type { BrowserCommand, DispatchedCommand, NewPageOptions, OwnedBrowser, OutputRedactor, PageNavigation, PageReading, SessionRuntime, TextQuery, WebRuntimeIdentity, WebSession } from './contract.ts';
|
|
8
|
+
import type { BrowserCommand, DispatchedCommand, FilesCapability, NewPageOptions, OwnedBrowser, OutputRedactor, PageNavigation, PageReading, SessionRuntime, TextQuery, WebRuntimeIdentity, WebSession } from './contract.ts';
|
|
7
9
|
import type { DiagnosticCollection, DiagnosticSink } from '../diagnostics/observations.ts';
|
|
8
10
|
import type { DiagnosticScope } from '../protocol/diagnostics.ts';
|
|
9
11
|
import type { CommandResult } from '../protocol/commands.ts';
|
|
@@ -11,6 +13,7 @@ import type { StorageState } from '../protocol/storage-state.ts';
|
|
|
11
13
|
import { Deadline } from '../protocol/deadline.ts';
|
|
12
14
|
import { CdpConnection } from './cdp/connection.ts';
|
|
13
15
|
import { LaunchError } from './contract.ts';
|
|
16
|
+
import { type RouteRemoval } from './routes.ts';
|
|
14
17
|
/** How to launch an Electron app. Every path is absolute. */
|
|
15
18
|
export type ElectronLaunchOptions = {
|
|
16
19
|
/** The Electron binary, inside Electron.app on macOS. */
|
|
@@ -168,6 +171,8 @@ export declare class ElectronWindowSession implements WebSession {
|
|
|
168
171
|
#private;
|
|
169
172
|
constructor(parts: ElectronWindowParts);
|
|
170
173
|
get url(): string | undefined;
|
|
174
|
+
/** The window's file pickers, caught as a browser's page catches them. The app saves its downloads its own way. */
|
|
175
|
+
get files(): FilesCapability | undefined;
|
|
171
176
|
execute(command: BrowserCommand, timeoutMs: number, signal?: AbortSignal, commandToken?: number): Promise<CommandResult>;
|
|
172
177
|
identify(session: SessionIdentity): void;
|
|
173
178
|
frameSource(identity: RecordIdentity): FrameSource;
|
|
@@ -176,6 +181,12 @@ export declare class ElectronWindowSession implements WebSession {
|
|
|
176
181
|
screenshot(timeoutMs: number): Promise<Uint8Array>;
|
|
177
182
|
readPage(queries: readonly TextQuery[], timeoutMs: number): Promise<PageReading>;
|
|
178
183
|
onNavigation(listener: (navigation: PageNavigation) => void): () => void;
|
|
184
|
+
armDialog(timeoutMs: number, signal?: AbortSignal): DialogArming;
|
|
185
|
+
answerDialog(id: string, reply: DialogReply, timeoutMs: number): Promise<RequestResult>;
|
|
186
|
+
onUnwaitedDialog(listener: (dialog: UnwaitedDialog) => void): () => void;
|
|
187
|
+
armPopup(): Promise<PopupArming>;
|
|
188
|
+
addRoute(): Promise<RequestResult>;
|
|
189
|
+
removeRoute(): Promise<RouteRemoval>;
|
|
179
190
|
collectDiagnostics(sink: DiagnosticSink, timeoutMs: number): Promise<DiagnosticCollection>;
|
|
180
191
|
captureState(_timeoutMs: number): Promise<StorageState>;
|
|
181
192
|
/** Quits the app, which the window belongs to. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"electron.d.ts","sourceRoot":"","sources":["../../src/browser/electron.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"electron.d.ts","sourceRoot":"","sources":["../../src/browser/electron.ts"],"names":[],"mappings":"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"}
|
package/dist/browser/electron.js
CHANGED
|
@@ -17,9 +17,13 @@ import { readProtocol, request, sendOptions } from "./cdp-results.js";
|
|
|
17
17
|
import { ChromiumProcess } from "./chromium-process.js";
|
|
18
18
|
import { closeGraceMs, LaunchError } from "./contract.js";
|
|
19
19
|
import { worldName } from "./isolated-world.js";
|
|
20
|
+
import { ElectronWindowWatch } from "./electron-window-watch.js";
|
|
20
21
|
import { describeDiagnostic, handshake, launchOutput, logLength, logWriter } from "./launch.js";
|
|
21
22
|
import { Listeners } from "./listeners.js";
|
|
23
|
+
import { browserCommandLocators, framesUnreached } from "./frames.js";
|
|
22
24
|
import { ChromiumPage } from "./page.js";
|
|
25
|
+
import { keepFocus } from "./popups.js";
|
|
26
|
+
import { routesRefused } from "./routes.js";
|
|
23
27
|
import { changeScript } from "./page-scripts.js";
|
|
24
28
|
import { createTemporaryProfile, removeStaleProfiles } from "./profiles.js";
|
|
25
29
|
import { explainStartFailure } from "./start-failure.js";
|
|
@@ -85,8 +89,10 @@ export async function launchElectron(options, timeoutMs = launchTimeoutMs, trans
|
|
|
85
89
|
// Every command Retest sends names its own timeout; the launch budget bounds any that would not.
|
|
86
90
|
connection = new CdpConnection(transport(child.pipe), { timeoutMs, onDiagnostic: (diagnostic) => log(describeDiagnostic(diagnostic)) });
|
|
87
91
|
const windows = new AppWindows(connection, startedAt, log);
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
// The watch's request goes first, so the first window's dialogs are heard from its first document on.
|
|
93
|
+
const watch = new ElectronWindowWatch(connection);
|
|
94
|
+
const [, version] = await Promise.all([watch.start(deadline), handshake(connection, deadline)]);
|
|
95
|
+
// Checked before any window is set up as a page, so a browser given in place of Electron is refused at once.
|
|
90
96
|
const electron = await readElectronVersion(executable, version.userAgent);
|
|
91
97
|
if (electron === undefined)
|
|
92
98
|
throw new NotElectronError(executable);
|
|
@@ -95,10 +101,18 @@ export async function launchElectron(options, timeoutMs = launchTimeoutMs, trans
|
|
|
95
101
|
windows.alreadyOpen(targetInfos.map((target) => target.targetId));
|
|
96
102
|
await request(connection, 'Target.setDiscoverTargets', { discover: true }, s.object({}), sendOptions(deadline));
|
|
97
103
|
const first = await windows.first(deadline);
|
|
98
|
-
const
|
|
104
|
+
const attached = await watch.first(deadline);
|
|
105
|
+
if (attached === undefined)
|
|
106
|
+
throw new NoWindowError(deadline.budgetMs);
|
|
107
|
+
const { session } = attached;
|
|
99
108
|
const onListenerError = (error) => log(`a listener failed: ${errorMessage(error)}`);
|
|
100
|
-
const pageOptions = { connection, session, browserContextId: first.browserContextId, onListenerError };
|
|
101
|
-
const
|
|
109
|
+
const pageOptions = { connection, session, browserContextId: attached.browserContextId === '' ? first.browserContextId : attached.browserContextId, onListenerError };
|
|
110
|
+
const answeredDialogs = () => watch.handOver();
|
|
111
|
+
const page = await ChromiumPage.open({ ...pageOptions, baseUrl: undefined, emulation: undefined, restoredOrigins: [], proxyServer: undefined, answeredDialogs }, deadline);
|
|
112
|
+
// Chromium draws no animation frames for a window another window covers, or one left behind others, and every action
|
|
113
|
+
// waits on frames to see its element stand still. A window on a test machine is often not in front, so the page is
|
|
114
|
+
// made to act as focused, as a Chrome page is, which keeps its frames coming.
|
|
115
|
+
await keepFocus(session, deadline);
|
|
102
116
|
await observeChanges(session, deadline, (note) => {
|
|
103
117
|
windows.note(note);
|
|
104
118
|
log(note);
|
|
@@ -350,7 +364,10 @@ class ElectronApp {
|
|
|
350
364
|
}
|
|
351
365
|
async #close(timeoutMs) {
|
|
352
366
|
this.#closeRequested = true;
|
|
353
|
-
|
|
367
|
+
const graceMs = Math.max(0, Math.min(closeGraceMs, timeoutMs - closeGraceMs));
|
|
368
|
+
// The app's helpers are read before its pipe closes, without holding the thread, within the allowance `stop` has.
|
|
369
|
+
const ownershipDeadline = new Deadline(graceMs + 2 * closeGraceMs);
|
|
370
|
+
await this.#process.recordDescendantsNow(ownershipDeadline);
|
|
354
371
|
const problems = [];
|
|
355
372
|
try {
|
|
356
373
|
this.#connection.close();
|
|
@@ -358,8 +375,7 @@ class ElectronApp {
|
|
|
358
375
|
catch (error) {
|
|
359
376
|
problems.push(`Could not close the Electron app's debugging pipe: ${errorMessage(error)}`);
|
|
360
377
|
}
|
|
361
|
-
|
|
362
|
-
problems.push(...await this.#process.stop(graceMs), ...this.#cleanupProblems);
|
|
378
|
+
problems.push(...await this.#process.stop(graceMs, ownershipDeadline), ...this.#cleanupProblems);
|
|
363
379
|
const { app, target, launch, windowsFile } = this.#named;
|
|
364
380
|
const notes = this.#windows.notes;
|
|
365
381
|
const record = { app, target, launch, pid: this.pid, windows: this.#windows.records, ...(notes.length === 0 ? {} : { notes }) };
|
|
@@ -405,6 +421,10 @@ export class ElectronWindowSession {
|
|
|
405
421
|
get url() {
|
|
406
422
|
return this.#window.url;
|
|
407
423
|
}
|
|
424
|
+
/** The window's file pickers, caught as a browser's page catches them. The app saves its downloads its own way. */
|
|
425
|
+
get files() {
|
|
426
|
+
return this.#window.files;
|
|
427
|
+
}
|
|
408
428
|
async execute(command, timeoutMs, signal, commandToken) {
|
|
409
429
|
return (await this.dispatch(command, timeoutMs, signal, commandToken)).result;
|
|
410
430
|
}
|
|
@@ -419,6 +439,10 @@ export class ElectronWindowSession {
|
|
|
419
439
|
async dispatch(command, timeoutMs, signal, commandToken) {
|
|
420
440
|
if (command.kind === 'goto')
|
|
421
441
|
return { result: { ok: false, failure: this.#noAddress() }, input: 'not_sent' };
|
|
442
|
+
// No test has looked inside a frame of an Electron window yet, so a frame there is refused by name.
|
|
443
|
+
const unreached = framesUnreached(browserCommandLocators(command), `the window of the Electron app ${this.#app}`);
|
|
444
|
+
if (unreached !== undefined)
|
|
445
|
+
return { result: { ok: false, failure: unreached }, input: 'not_sent' };
|
|
422
446
|
const answer = await this.#window.dispatch(command, timeoutMs, signal, commandToken);
|
|
423
447
|
if (answer.result.ok || !this.#firstClosed())
|
|
424
448
|
return answer;
|
|
@@ -439,6 +463,27 @@ export class ElectronWindowSession {
|
|
|
439
463
|
onNavigation(listener) {
|
|
440
464
|
return this.#window.onNavigation(listener);
|
|
441
465
|
}
|
|
466
|
+
// A dialog in the app's first window is handed over and answered as on a browser's page.
|
|
467
|
+
armDialog(timeoutMs, signal) {
|
|
468
|
+
return this.#window.armDialog?.(timeoutMs, signal) ?? { ok: false, failure: { class: 'unsupported', message: `The window of the Electron app ${this.#app} cannot hand its dialogs to a test.` } };
|
|
469
|
+
}
|
|
470
|
+
answerDialog(id, reply, timeoutMs) {
|
|
471
|
+
return this.#window.answerDialog?.(id, reply, timeoutMs) ?? Promise.resolve({ ok: false, failure: { class: 'unsupported', message: `The window of the Electron app ${this.#app} cannot answer a dialog for a test.` } });
|
|
472
|
+
}
|
|
473
|
+
onUnwaitedDialog(listener) {
|
|
474
|
+
return this.#window.onUnwaitedDialog?.(listener) ?? (() => undefined);
|
|
475
|
+
}
|
|
476
|
+
// A test reaches only the app's first window, so a window the app opens is out of its reach.
|
|
477
|
+
async armPopup() {
|
|
478
|
+
return { ok: false, failure: { class: 'unsupported', message: `A test reaches only the first window of the Electron app ${this.#app}, so page.popup() is for a browser's page.` } };
|
|
479
|
+
}
|
|
480
|
+
// The app's window holds no route: Retest has not checked how an app's own requests meet the window's Fetch domain.
|
|
481
|
+
async addRoute() {
|
|
482
|
+
return { ok: false, failure: routesRefused('in an Electron app') };
|
|
483
|
+
}
|
|
484
|
+
async removeRoute() {
|
|
485
|
+
return { ok: false, failure: routesRefused('in an Electron app') };
|
|
486
|
+
}
|
|
442
487
|
// The app opens its window as it starts, before any test asks for it, while a browser's page is still blank by then. A
|
|
443
488
|
// window already showing a page once capture runs has had time to log and load what nobody heard. The window itself
|
|
444
489
|
// is asked, after capture started, since the page may have missed the commit of a document that came first.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BrowserCommand, DispatchedCommand } from './contract.ts';
|
|
2
2
|
import type { DocumentFacts } from './document-facts.ts';
|
|
3
3
|
import type { Point } from './input.ts';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Guard } from './input-guard.ts';
|
|
5
|
+
import type { InDocument, PageWorld, WorldArguments } from './isolated-world.ts';
|
|
5
6
|
import type { Observation } from '../protocol/commands.ts';
|
|
6
7
|
import type { Deadline } from '../protocol/deadline.ts';
|
|
7
8
|
import type { EmptyStep, LocatorRecipe } from '../protocol/locator.ts';
|
|
@@ -11,10 +12,11 @@ import type { Schema } from '../protocol/schema.ts';
|
|
|
11
12
|
declare const checks: readonly ["attached", "visible", "enabled", "editable", "stable", "in-view", "hit-target", "focused"];
|
|
12
13
|
/** An actionability check, in the order `prepare` runs them after the element is found. */
|
|
13
14
|
export type Check = (typeof checks)[number];
|
|
14
|
-
declare const unsupportedReasons: readonly ["field", "multiline", "checkable", "radio", "select", "multiple"];
|
|
15
|
+
declare const unsupportedReasons: readonly ["field", "multiline", "checkable", "radio", "select", "multiple", "toggle"];
|
|
15
16
|
/**
|
|
16
17
|
* Why an element cannot take an action at all: not a field `fill` can use, a line break for a one-line field, not a
|
|
17
|
-
* control `check` can use, a radio button to uncheck, not a `<select>`,
|
|
18
|
+
* control `check` can use, a radio button to uncheck, not a `<select>`, a list for a select that takes one, or a
|
|
19
|
+
* select whose keys would toggle an option, on an engine that sends no such key.
|
|
18
20
|
*/
|
|
19
21
|
export type UnsupportedReason = (typeof unsupportedReasons)[number];
|
|
20
22
|
/** One key of a select's plan: a character to type, or `Home`, `ArrowDown` or `Space`, with the modifier that leaves the selection alone. */
|
|
@@ -98,7 +100,8 @@ export type Pointer = 'click' | 'tap';
|
|
|
98
100
|
* `allowedOrigins` are the only origins a `fill` may type into. A `press` names its key as the test wrote it, and how
|
|
99
101
|
* many keys it holds and presses. A `check` or `uncheck` clicks through `pointer`. A `select` names its choices, and
|
|
100
102
|
* `multiple` when the test gave a list; with `typing`, it is one key of the select's plan, which holds `strokes` keys.
|
|
101
|
-
*
|
|
103
|
+
* `toggleRefusal`, on an engine that sends no key that toggles an option, says why: a select that needs one is refused
|
|
104
|
+
* as soon as it is found, before any check that waits. A `scroll` turns the wheel, and a `hover` moves the mouse.
|
|
102
105
|
*/
|
|
103
106
|
export type ActionIntent = {
|
|
104
107
|
multiline: boolean;
|
|
@@ -126,9 +129,38 @@ export type ActionIntent = {
|
|
|
126
129
|
typing?: {
|
|
127
130
|
strokes: number;
|
|
128
131
|
};
|
|
132
|
+
toggleRefusal?: string;
|
|
129
133
|
});
|
|
130
134
|
/** A look at the page for input that goes to the document, not to an element: a key or the wheel. */
|
|
131
135
|
export type DocumentAction = 'press' | 'scroll';
|
|
136
|
+
/** A look that readied an element, as `prepare` answers. */
|
|
137
|
+
export type ReadyReadiness = Extract<Readiness, {
|
|
138
|
+
status: 'ready';
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* A look for an action, in the document it ran in. For an element in a frame, `around` holds the guards the look armed
|
|
142
|
+
* in the documents around the frame, outermost first.
|
|
143
|
+
*/
|
|
144
|
+
export type Prepared = InDocument<Readiness> & {
|
|
145
|
+
around?: readonly Guard[];
|
|
146
|
+
};
|
|
147
|
+
/** The pointer input a frame's element passes on to an element inside it, which the documents around the frame guard. */
|
|
148
|
+
export type PointerThrough = Pointer | 'hover' | 'scroll';
|
|
149
|
+
/**
|
|
150
|
+
* A world in the document of a frame, which finds the frame again by its element for every call. A look there reads
|
|
151
|
+
* the frame's own document. `missing` says what an error a call threw was found missing: the element of one of the
|
|
152
|
+
* frames, as an empty step that names that frame, or null when the element is there and its document is not yet;
|
|
153
|
+
* undefined for any other error. `place` moves the point an action readied in the frame's document to the page's
|
|
154
|
+
* viewport, where input lands, after it checks the frame in each document around it, and for `pointer` input arms
|
|
155
|
+
* each of those documents' guards for the frame's element. `pageFacts` reads the page's own address and title.
|
|
156
|
+
*/
|
|
157
|
+
export interface FramedWorld extends PageWorld {
|
|
158
|
+
/** As `enter`, with each frame on the path brought into view first, as an action brings its element into view. */
|
|
159
|
+
enterInView<T>(functionDeclaration: string, args: WorldArguments, schema: Schema<T>, deadline: Deadline): Promise<InDocument<T>>;
|
|
160
|
+
missing(error: unknown): EmptyStep | null | undefined;
|
|
161
|
+
place(ready: InDocument<ReadyReadiness>, pointer: PointerThrough | null, deadline: Deadline): Promise<Prepared>;
|
|
162
|
+
pageFacts(deadline: Deadline): Promise<DocumentFacts>;
|
|
163
|
+
}
|
|
132
164
|
/** A CSS selector of a locator's step that the page could not read, with the browser's reason. */
|
|
133
165
|
export type InvalidSelector = {
|
|
134
166
|
step: number;
|
|
@@ -146,8 +178,11 @@ export type Observed = {
|
|
|
146
178
|
} | {
|
|
147
179
|
shadow: string;
|
|
148
180
|
};
|
|
149
|
-
/**
|
|
150
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Resolves the locator once and reads what an assertion needs, and the page it read it on. In a frame whose element
|
|
183
|
+
* is not there, it reads no match, on the page the frame was looked for in.
|
|
184
|
+
*/
|
|
185
|
+
export declare function observe(world: PageWorld, locator: LocatorRecipe, deadline: Deadline): Promise<Observed>;
|
|
151
186
|
/** One locator's read in a keyed look: its observation, and the key of each element it lists, in the same order. */
|
|
152
187
|
export type KeyedLook = {
|
|
153
188
|
observation: Observation;
|
|
@@ -172,7 +207,7 @@ export type KeyedObserved = {
|
|
|
172
207
|
* listed with the key the document's world keeps for that node. An answer that does not read each locator once, or
|
|
173
208
|
* does not key each element it lists, is not Retest's page function answering, and throws. Sends no input.
|
|
174
209
|
*/
|
|
175
|
-
export declare function observeKeyed(world:
|
|
210
|
+
export declare function observeKeyed(world: PageWorld, locators: readonly LocatorRecipe[], deadline: Deadline): Promise<KeyedObserved>;
|
|
176
211
|
/**
|
|
177
212
|
* Sends one command through `dispatch` with every look that readies its element pinned to the node `key` names: a
|
|
178
213
|
* look whose locator finds another node answers `moved`, which fails the command at once with no input sent. A command
|
|
@@ -193,9 +228,10 @@ export declare function pinnedKey(): string | undefined;
|
|
|
193
228
|
* Resolves the locator once and runs the checks an action needs, readying a field for `fill` and focusing the
|
|
194
229
|
* element for `press`, or for one key of a `select`'s plan. Also names the document it looked at, which holds the
|
|
195
230
|
* guard a ready element armed. A `select` that is not yet typing is only checked here, and planned. Inside
|
|
196
|
-
* `dispatchPinned` the one match must be the pinned node.
|
|
231
|
+
* `dispatchPinned` the one match must be the pinned node. In a frame, the checks run in the frame's document, and the
|
|
232
|
+
* point to act at is then placed in the page's viewport; a frame whose element is not there is missing, as an element is.
|
|
197
233
|
*/
|
|
198
|
-
export declare function prepare(world:
|
|
234
|
+
export declare function prepare(world: PageWorld, locator: LocatorRecipe, intent: ActionIntent, deadline: Deadline): Promise<Prepared>;
|
|
199
235
|
/** Whether a select holds exactly the options its choices name, the labels of those it holds, and the page it is on. */
|
|
200
236
|
export type Selection = {
|
|
201
237
|
status: 'selected' | 'other' | 'lost';
|
|
@@ -226,12 +262,12 @@ export type TypedSelect = {
|
|
|
226
262
|
* undefined once that document has gone: a select another document holds is never read in its place. Inside
|
|
227
263
|
* `dispatchPinned` it reads the pinned select only, and another in its place is `lost`. Sends no input.
|
|
228
264
|
*/
|
|
229
|
-
export declare function readSelection(world:
|
|
265
|
+
export declare function readSelection(world: PageWorld, { locator, choices, document }: TypedSelect, deadline: Deadline): Promise<Selection | undefined>;
|
|
230
266
|
/**
|
|
231
267
|
* Whether `document` set off for another document since the arming `token` began, or has gone already. A select's
|
|
232
268
|
* next key goes only into a document that is staying. Sends no input.
|
|
233
269
|
*/
|
|
234
|
-
export declare function pageSetOff(world:
|
|
270
|
+
export declare function pageSetOff(world: PageWorld, { document, token }: {
|
|
235
271
|
document: number;
|
|
236
272
|
token: number;
|
|
237
273
|
}, deadline: Deadline): Promise<boolean>;
|
|
@@ -240,14 +276,14 @@ export declare function pageSetOff(world: IsolatedWorld, { document, token }: {
|
|
|
240
276
|
* to whatever holds the focus there, with the keys it holds and presses, or the wheel at the centre of the viewport.
|
|
241
277
|
* Ready as `prepare` is, in that document.
|
|
242
278
|
*/
|
|
243
|
-
export declare function armDocument(world:
|
|
279
|
+
export declare function armDocument(world: PageWorld, action: DocumentAction, strokes: number, deadline: Deadline): Promise<InDocument<Extract<Readiness, {
|
|
244
280
|
status: 'ready';
|
|
245
281
|
}>>>;
|
|
246
282
|
/**
|
|
247
|
-
* Whether the one element the locator finds is checked, or null when no single such control is there
|
|
248
|
-
* `dispatchPinned` it reads the pinned element only, and another in its place is null.
|
|
283
|
+
* Whether the one element the locator finds is checked, or null when no single such control is there, a frame it is
|
|
284
|
+
* in included. Inside `dispatchPinned` it reads the pinned element only, and another in its place is null.
|
|
249
285
|
*/
|
|
250
|
-
export declare function readChecked(world:
|
|
286
|
+
export declare function readChecked(world: PageWorld, locator: LocatorRecipe, deadline: Deadline): Promise<boolean | null>;
|
|
251
287
|
/**
|
|
252
288
|
* An action as failure messages name it, the way a test reads: a press names its key, a select its choices, and
|
|
253
289
|
* a key for the page's keyboard or a wheel for the page has no element.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element-queries.d.ts","sourceRoot":"","sources":["../../src/browser/element-queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"element-queries.d.ts","sourceRoot":"","sources":["../../src/browser/element-queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { isGoneContext } from "./isolated-world.js";
|
|
|
9
9
|
import { locatorArguments, locatorsArguments } from "./locate.js";
|
|
10
10
|
import { armDocumentFunction, checkedFunction, keyedObserveFunction, observeFunction, prepareFunction, selectionFunction, setOffFunction } from "./page-scripts.js";
|
|
11
11
|
const checks = ['attached', 'visible', 'enabled', 'editable', 'stable', 'in-view', 'hit-target', 'focused'];
|
|
12
|
-
const unsupportedReasons = ['field', 'multiline', 'checkable', 'radio', 'select', 'multiple'];
|
|
12
|
+
const unsupportedReasons = ['field', 'multiline', 'checkable', 'radio', 'select', 'multiple', 'toggle'];
|
|
13
13
|
const pageFacts = documentFactsSchema;
|
|
14
14
|
const planSchema = s.object({
|
|
15
15
|
quietMs: s.number({ integer: true, min: 0 }),
|
|
@@ -43,16 +43,42 @@ const readinessSchema = s.discriminatedUnion('status', [
|
|
|
43
43
|
s.object({ status: s.literal('unchanged'), page: pageFacts }),
|
|
44
44
|
readySchema,
|
|
45
45
|
]);
|
|
46
|
+
// The context of a look that found no frame, which has no document. Nothing reads the context of an unready look.
|
|
47
|
+
const noDocument = -1;
|
|
48
|
+
function framedWorld(world) {
|
|
49
|
+
return isFramed(world) ? world : undefined;
|
|
50
|
+
}
|
|
51
|
+
// Only a frame's world places a point, and it answers the rest of `FramedWorld` with it.
|
|
52
|
+
function isFramed(world) {
|
|
53
|
+
return 'place' in world;
|
|
54
|
+
}
|
|
55
|
+
// What a look reads when a frame of its locator is not there: no match, and the frame that kept nothing.
|
|
56
|
+
function unseen(empty) {
|
|
57
|
+
const missing = empty === null ? {} : { emptyStep: empty };
|
|
58
|
+
return { count: 0, visible: null, text: null, value: null, checked: null, enabled: null, items: [], itemsTruncated: false, ...missing };
|
|
59
|
+
}
|
|
46
60
|
const invalidSchema = s.object({ step: s.number({ integer: true, min: 0 }), message: s.string() });
|
|
47
61
|
const observedSchema = s.union([
|
|
48
62
|
s.object({ observation: observationSchema, page: pageFacts }),
|
|
49
63
|
s.object({ invalid: invalidSchema }),
|
|
50
64
|
s.object({ shadow: s.string() }),
|
|
51
65
|
]);
|
|
52
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Resolves the locator once and reads what an assertion needs, and the page it read it on. In a frame whose element
|
|
68
|
+
* is not there, it reads no match, on the page the frame was looked for in.
|
|
69
|
+
*/
|
|
53
70
|
export async function observe(world, locator, deadline) {
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
try {
|
|
72
|
+
const observed = await world.call(observeFunction, locatorArguments(locator, [observedItemLimit]), observedSchema, deadline);
|
|
73
|
+
return 'observation' in observed ? { observation: observed.observation, page: pageFactsOf(observed.page) } : observed;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const framed = framedWorld(world);
|
|
77
|
+
const missing = framed?.missing(error);
|
|
78
|
+
if (framed === undefined || missing === undefined)
|
|
79
|
+
throw error;
|
|
80
|
+
return { observation: unseen(missing), page: pageFactsOf(await framed.pageFacts(deadline)) };
|
|
81
|
+
}
|
|
56
82
|
}
|
|
57
83
|
const keyedSchema = s.union([
|
|
58
84
|
s.object({ reads: s.array(s.object({ observation: observationSchema, keys: s.array(s.string()) })), page: pageFacts }),
|
|
@@ -65,7 +91,17 @@ const keyedSchema = s.union([
|
|
|
65
91
|
* does not key each element it lists, is not Retest's page function answering, and throws. Sends no input.
|
|
66
92
|
*/
|
|
67
93
|
export async function observeKeyed(world, locators, deadline) {
|
|
68
|
-
|
|
94
|
+
let observed;
|
|
95
|
+
try {
|
|
96
|
+
observed = await world.call(keyedObserveFunction, locatorsArguments(locators, [observedItemLimit]), keyedSchema, deadline);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const framed = framedWorld(world);
|
|
100
|
+
const missing = framed?.missing(error);
|
|
101
|
+
if (framed === undefined || missing === undefined)
|
|
102
|
+
throw error;
|
|
103
|
+
return { reads: locators.map(() => ({ observation: unseen(missing), keys: [] })), page: pageFactsOf(await framed.pageFacts(deadline)) };
|
|
104
|
+
}
|
|
69
105
|
if ('invalid' in observed)
|
|
70
106
|
return { invalid: observed.invalid, locator: observed.query };
|
|
71
107
|
if ('shadow' in observed)
|
|
@@ -108,12 +144,46 @@ export function pinnedKey() {
|
|
|
108
144
|
* Resolves the locator once and runs the checks an action needs, readying a field for `fill` and focusing the
|
|
109
145
|
* element for `press`, or for one key of a `select`'s plan. Also names the document it looked at, which holds the
|
|
110
146
|
* guard a ready element armed. A `select` that is not yet typing is only checked here, and planned. Inside
|
|
111
|
-
* `dispatchPinned` the one match must be the pinned node.
|
|
147
|
+
* `dispatchPinned` the one match must be the pinned node. In a frame, the checks run in the frame's document, and the
|
|
148
|
+
* point to act at is then placed in the page's viewport; a frame whose element is not there is missing, as an element is.
|
|
112
149
|
*/
|
|
113
|
-
export function prepare(world, locator, intent, deadline) {
|
|
150
|
+
export async function prepare(world, locator, intent, deadline) {
|
|
114
151
|
const pinned = pinnedElement.getStore();
|
|
115
152
|
const asked = pinned === undefined ? pageIntent(intent) : { ...pageIntent(intent), element: pinned };
|
|
116
|
-
|
|
153
|
+
const framed = framedWorld(world);
|
|
154
|
+
if (framed === undefined)
|
|
155
|
+
return world.enter(prepareFunction, locatorArguments(locator, [asked]), readinessSchema, deadline);
|
|
156
|
+
let seen;
|
|
157
|
+
try {
|
|
158
|
+
seen = await framed.enterInView(prepareFunction, locatorArguments(locator, [asked]), readinessSchema, deadline);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const missing = framed.missing(error);
|
|
162
|
+
if (missing === undefined)
|
|
163
|
+
throw error;
|
|
164
|
+
return { value: { status: 'missing', empty: missing }, context: noDocument };
|
|
165
|
+
}
|
|
166
|
+
const { value, context } = seen;
|
|
167
|
+
// A key goes to the focus, which the frame took with the element; only a point is placed in the page.
|
|
168
|
+
return value.status === 'ready' && value.point !== null ? framed.place({ value, context }, pointerThrough(intent), deadline) : seen;
|
|
169
|
+
}
|
|
170
|
+
// The pointer input an action sends, which goes through each frame's element at its point; null for keys and typing,
|
|
171
|
+
// which go to the focus.
|
|
172
|
+
function pointerThrough(intent) {
|
|
173
|
+
switch (intent.action) {
|
|
174
|
+
case 'click':
|
|
175
|
+
case 'tap':
|
|
176
|
+
case 'hover':
|
|
177
|
+
case 'scroll':
|
|
178
|
+
return intent.action;
|
|
179
|
+
case 'check':
|
|
180
|
+
case 'uncheck':
|
|
181
|
+
return intent.pointer;
|
|
182
|
+
case 'fill':
|
|
183
|
+
case 'press':
|
|
184
|
+
case 'select':
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
117
187
|
}
|
|
118
188
|
export const pageSelectionSchema = s.object({
|
|
119
189
|
status: s.enum(['selected', 'other', 'lost']),
|
|
@@ -166,11 +236,18 @@ export function armDocument(world, action, strokes, deadline) {
|
|
|
166
236
|
return world.enter(armDocumentFunction, [action, strokes], readySchema, deadline);
|
|
167
237
|
}
|
|
168
238
|
/**
|
|
169
|
-
* Whether the one element the locator finds is checked, or null when no single such control is there
|
|
170
|
-
* `dispatchPinned` it reads the pinned element only, and another in its place is null.
|
|
239
|
+
* Whether the one element the locator finds is checked, or null when no single such control is there, a frame it is
|
|
240
|
+
* in included. Inside `dispatchPinned` it reads the pinned element only, and another in its place is null.
|
|
171
241
|
*/
|
|
172
|
-
export function readChecked(world, locator, deadline) {
|
|
173
|
-
|
|
242
|
+
export async function readChecked(world, locator, deadline) {
|
|
243
|
+
try {
|
|
244
|
+
return await world.call(checkedFunction, locatorArguments(locator, [], pinnedKey()), s.nullable(s.boolean()), deadline);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (framedWorld(world)?.missing(error) === undefined)
|
|
248
|
+
throw error;
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
174
251
|
}
|
|
175
252
|
/**
|
|
176
253
|
* An action as failure messages name it, the way a test reads: a press names its key, a select its choices, and
|
|
@@ -208,7 +285,9 @@ function pageIntent(intent) {
|
|
|
208
285
|
return { action: 'check', checked: intent.action === 'check', pointer: intent.pointer };
|
|
209
286
|
case 'select': {
|
|
210
287
|
const typing = intent.typing === undefined ? {} : { typing: true, strokes: intent.typing.strokes };
|
|
211
|
-
|
|
288
|
+
// The page needs only to know that no toggle goes; the reason stays here, for the failure.
|
|
289
|
+
const toggles = intent.toggleRefusal === undefined ? {} : { toggles: false };
|
|
290
|
+
return { action: 'select', choices: intent.choices, multiple: intent.multiple, ...typing, ...toggles };
|
|
212
291
|
}
|
|
213
292
|
case 'press':
|
|
214
293
|
return { action: 'press', strokes: intent.strokes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emulation.d.ts","sourceRoot":"","sources":["../../src/browser/emulation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAMvD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"emulation.d.ts","sourceRoot":"","sources":["../../src/browser/emulation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAMvD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUjH"}
|