@itmetsam/samurai-framework 0.22.10 → 0.22.11

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.
@@ -0,0 +1,58 @@
1
+ import Locator from "../locator/locator.js";
2
+ import { attributeSelector, cssSelector, labelSelector, roleSelector, testIdSelector, textSelector, } from "../locator/selector.js";
3
+ import { PageActions } from "./page-actions.js";
4
+ /** @internal The ways to locate elements */
5
+ export class PageLocating extends PageActions {
6
+ /**
7
+ * Locates elements on the page. An xpath (`//x`, `/html/body`, `(//x)[2]`, `./x`, `xpath=…`, or an older
8
+ * relative one such as `a[@id='x']`) or a css selector (`input[name='x']`, `a, button`, `css=…`).
9
+ * `options` filter the matches, as `locator.filter()` does
10
+ */
11
+ locator(selector, options) {
12
+ const found = this.locators.locate(selector);
13
+ return options === undefined ? found : found.filter(options);
14
+ }
15
+ /**
16
+ * Makes this page's locators strict (or not): an action or read on a locator matching several elements then
17
+ * throws `name(): strict mode violation, N elements match ...` instead of using the first. `undefined` goes back
18
+ * to config `use.strictSelectors` (off unless set). Applies to locators made afterwards and to earlier ones that have not
19
+ * run an action or read yet: a locator keeps the answer it got at its first one
20
+ */
21
+ setStrictSelectors(enabled) {
22
+ this.locators.setStrictSelectors(enabled);
23
+ }
24
+ /** Locates elements with a CSS selector */
25
+ getByCss(css) {
26
+ return this.locators.locate([cssSelector(css)]);
27
+ }
28
+ /** Locates elements by their text: an exact match unless `match: "partial"` or `exact: false`; a RegExp is tested as written (see `TextOptions`) */
29
+ getByText(text, options) {
30
+ return this.locators.locate([textSelector(text, options)]);
31
+ }
32
+ /** Locates form controls by their label text: an exact match unless `match: "partial"` or `exact: false`; a RegExp is tested as written */
33
+ getByLabel(text, options) {
34
+ return this.locators.locate([labelSelector(text, options)]);
35
+ }
36
+ /** Locates elements by their `placeholder` (see `TextOptions` for `exact` and RegExp) */
37
+ getByPlaceholder(text, options) {
38
+ return this.locators.locate([
39
+ attributeSelector("placeholder", text, options),
40
+ ]);
41
+ }
42
+ /** Locates elements by their `alt` text (images, areas, image inputs) */
43
+ getByAltText(text, options) {
44
+ return this.locators.locate([attributeSelector("alt", text, options)]);
45
+ }
46
+ /** Locates elements by their `title` attribute */
47
+ getByTitle(text, options) {
48
+ return this.locators.locate([attributeSelector("title", text, options)]);
49
+ }
50
+ /** Locates elements by their `data-testid` attribute */
51
+ getByTestId(testId) {
52
+ return this.locators.locate([testIdSelector(testId)]);
53
+ }
54
+ /** Locates elements by ARIA role and, optionally, accessible name and state (see `RoleOptions`) */
55
+ getByRole(role, options) {
56
+ return this.locators.locate([roleSelector(role, options)]);
57
+ }
58
+ }
@@ -0,0 +1,120 @@
1
+ import type { NetworkRequest } from "../network/network-tracker.js";
2
+ import type { Download } from "./download.js";
3
+ import type { FileChooser } from "./file-chooser.js";
4
+ import type { LogRecord, PageError } from "./page-logs.js";
5
+ import type { RouteHandler } from "../network/router.js";
6
+ import { Response } from "../network/response.js";
7
+ import type { NetworkMatch } from "../network/network-wait.js";
8
+ import type { UrlPattern } from "../network/url-match.js";
9
+ import { type PageEventName, type PageEvents, type WaitableEventName, type WaitForEventOptions, type WaitOptions } from "./page-events.js";
10
+ import { PageLocating } from "./page-locating.js";
11
+ /** @internal Events, network, routes, logs, dialogs and transfers */
12
+ export declare abstract class PageObserving extends PageLocating {
13
+ /**
14
+ * Starts following this page's requests now. Tracking only sees requests made after it started, so the
15
+ * browser calls this when it opens a page; without it, tracking starts on first use. Does nothing when
16
+ * config `network.track` is false.
17
+ */
18
+ startNetworkTracking(): Promise<void>;
19
+ /**
20
+ * Waits until no request has been in flight for `idleTime` ms (default config `network.idleTime`, then 500).
21
+ * Long-lived requests (server-sent events, long polling) keep the page busy until `timeout`.
22
+ */
23
+ waitForNetworkIdle(options?: {
24
+ idleTime?: number;
25
+ timeout?: number;
26
+ }): Promise<void>;
27
+ /**
28
+ * Listens for page events: `request`, `response`, `requestfailed` (network), `dialog` (a JavaScript dialog or
29
+ * `beforeunload` prompt opened; answer it with `dialog.accept()` / `dialog.dismiss()`), `console` (a `console.*` call)
30
+ * and `pageerror` (an uncaught exception). Starts what the event needs.
31
+ * A dialog nobody answers is dismissed automatically (a `beforeunload` prompt is accepted), so it cannot hang a test.
32
+ * @example
33
+ * page.on("dialog", (dialog) => dialog.accept());
34
+ */
35
+ on<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
36
+ once<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
37
+ off<E extends PageEventName>(event: E, listener: (...args: PageEvents[E]) => void): this;
38
+ private trackInBackground;
39
+ /** Starts following downloads; the browser does this when it opens a page, `page.on("download")` and `waitForDownload` when needed */
40
+ startDownloadTracking(): Promise<void>;
41
+ /** Starts following file pickers; the browser does this when it opens a page, `page.on("filechooser")` and `waitForFileChooser` when needed */
42
+ startFileChooserTracking(): Promise<void>;
43
+ /**
44
+ * Resolves with the next download this page starts (when it starts, not when it ends: use `download.path()` for that).
45
+ * Call it before the download is triggered, or pass `trigger`, which runs once the wait is set up.
46
+ * @example
47
+ * const download = await page.waitForDownload({ trigger: () => link.click() });
48
+ * const file = await download.path();
49
+ */
50
+ waitForDownload(options?: WaitOptions): Promise<Download>;
51
+ /**
52
+ * Resolves with the next file picker the page opens (a click on an `<input type=file>` or its label). Nothing shows
53
+ * on screen: answer it with `chooser.setFiles(...)`.
54
+ * @example
55
+ * const chooser = await page.waitForFileChooser({ trigger: () => page.locator("label[@for='avatar']").click() });
56
+ * await chooser.setFiles("./fixtures/avatar.png");
57
+ */
58
+ waitForFileChooser(options?: WaitOptions): Promise<FileChooser>;
59
+ /**
60
+ * Playwright-style wait for one page event: `download`, `filechooser`, `request` or `response`. Same as the named
61
+ * `waitFor…` method, with an optional predicate (a function, or `predicate` in the options). Call it before the event
62
+ * is caused, or pass `trigger`, which runs once the wait is set up.
63
+ * @example
64
+ * const chooser = await page.waitForEvent("filechooser", { trigger: () => page.getByText("Upload file").click() });
65
+ * await chooser.setFiles("./myfile.pdf");
66
+ */
67
+ waitForEvent<E extends WaitableEventName>(event: E, optionsOrPredicate?: ((item: PageEvents[E][0]) => boolean) | WaitForEventOptions<PageEvents[E][0]>): Promise<PageEvents[E][0]>;
68
+ /**
69
+ * Starts keeping response bodies, which is what makes `response.body()` possible: the browser only keeps a body
70
+ * when this ran before the request started. `page.on("response")` and `waitForResponse` call it; config
71
+ * `network.collectBodies` calls it when the browser opens the page.
72
+ */
73
+ startBodyCollection(): Promise<void>;
74
+ /**
75
+ * Resolves with the next response of this page whose URL matches (a pattern, see `route`) or that the function
76
+ * accepts. Call it before the request is made, or pass `trigger`, which runs once the wait is set up.
77
+ * @example
78
+ * const response = await page.waitForResponse("**\/api/users", { trigger: () => button.click() });
79
+ * const users = await response.json();
80
+ */
81
+ waitForResponse(match: NetworkMatch<Response>, options?: WaitOptions): Promise<Response>;
82
+ /** Like `waitForResponse`, for requests */
83
+ waitForRequest(match: NetworkMatch<NetworkRequest>, options?: WaitOptions): Promise<NetworkRequest>;
84
+ /** Turns the browser's HTTP cache off (`true`) or back on for this page: every request then goes to the server */
85
+ setCacheDisabled(disabled: boolean): Promise<void>;
86
+ /**
87
+ * Sends requests whose URL matches to `handler`, which must answer with `route.fulfill()`, `route.continue()`,
88
+ * `route.abort()` or `route.fallback()` (await the answer: a handler that returns first lets the request continue).
89
+ * A string without `*` is an exact URL, with `*` / `**` a glob (`*` stops at `/`), a RegExp is tested on the URL.
90
+ * Routes added later are asked first. Errors a handler throws fail the request and, in the runner, the test.
91
+ * @example
92
+ * await page.route("**\/api/users", (route) => route.fulfill({ json: [{ id: 1 }] }));
93
+ * await page.route(/\.(png|jpg)$/, (route) => route.abort());
94
+ */
95
+ route(url: UrlPattern, handler: RouteHandler): Promise<void>;
96
+ /** Removes the routes for `url` (only the one with `handler`, when given) */
97
+ unroute(url: UrlPattern, handler?: RouteHandler): Promise<void>;
98
+ unrouteAll(): Promise<void>;
99
+ /** Errors thrown by route handlers so far */
100
+ routeErrors(): readonly Error[];
101
+ /**
102
+ * Starts collecting the page's console output and uncaught exceptions. The browser does this when it opens a page so
103
+ * early output is kept; `page.on("console" | "pageerror")` starts it when needed.
104
+ */
105
+ startLogging(): Promise<void>;
106
+ /** The newest console and error entries of this page (empty until logging started), oldest first */
107
+ getLogs(): readonly LogRecord[];
108
+ /** Uncaught exceptions of the page since logging started or `clearLogs()` */
109
+ pageErrors(): readonly PageError[];
110
+ /** How many entries fell out of the page's log buffer */
111
+ get logsDropped(): number;
112
+ clearLogs(): void;
113
+ /** Resolves once everything the page logged before the call has arrived: events and replies share one ordered connection */
114
+ syncLogs(): Promise<void>;
115
+ /** Tells the runner not to fail this test on uncaught page exceptions (config `logs.failOnPageError`) */
116
+ allowPageErrors(): void;
117
+ get pageErrorsAllowed(): boolean;
118
+ /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
119
+ startDialogHandling(): Promise<void>;
120
+ }
@@ -0,0 +1,194 @@
1
+ import logger from "../logger/index.js";
2
+ import { Response } from "../network/response.js";
3
+ import { startSources, waitForEventOn, } from "./page-events.js";
4
+ import { PageLocating } from "./page-locating.js";
5
+ /** @internal Events, network, routes, logs, dialogs and transfers */
6
+ export class PageObserving extends PageLocating {
7
+ /**
8
+ * Starts following this page's requests now. Tracking only sees requests made after it started, so the
9
+ * browser calls this when it opens a page; without it, tracking starts on first use. Does nothing when
10
+ * config `network.track` is false.
11
+ */
12
+ startNetworkTracking() {
13
+ return this.net.startTracking();
14
+ }
15
+ /**
16
+ * Waits until no request has been in flight for `idleTime` ms (default config `network.idleTime`, then 500).
17
+ * Long-lived requests (server-sent events, long polling) keep the page busy until `timeout`.
18
+ */
19
+ waitForNetworkIdle(options = {}) {
20
+ return this.net.waitForIdle(options);
21
+ }
22
+ /**
23
+ * Listens for page events: `request`, `response`, `requestfailed` (network), `dialog` (a JavaScript dialog or
24
+ * `beforeunload` prompt opened; answer it with `dialog.accept()` / `dialog.dismiss()`), `console` (a `console.*` call)
25
+ * and `pageerror` (an uncaught exception). Starts what the event needs.
26
+ * A dialog nobody answers is dismissed automatically (a `beforeunload` prompt is accepted), so it cannot hang a test.
27
+ * @example
28
+ * page.on("dialog", (dialog) => dialog.accept());
29
+ */
30
+ on(event, listener) {
31
+ this.pageEvents.on(event, listener);
32
+ this.trackInBackground(event);
33
+ return this;
34
+ }
35
+ once(event, listener) {
36
+ this.pageEvents.once(event, listener);
37
+ this.trackInBackground(event);
38
+ return this;
39
+ }
40
+ off(event, listener) {
41
+ this.pageEvents.off(event, listener);
42
+ return this;
43
+ }
44
+ trackInBackground(event) {
45
+ const start = {
46
+ dialogs: () => this.startDialogHandling(),
47
+ downloads: () => this.startDownloadTracking(),
48
+ filechoosers: () => this.startFileChooserTracking(),
49
+ logs: () => this.startLogging(),
50
+ network: () => this.startNetworkTracking(),
51
+ bodies: () => this.startBodyCollection(),
52
+ };
53
+ startSources(event, start, (err) => logger.error("Could not start listening for %s", event, { err }));
54
+ }
55
+ /** Starts following downloads; the browser does this when it opens a page, `page.on("download")` and `waitForDownload` when needed */
56
+ startDownloadTracking() {
57
+ return this.transfers.startDownloadTracking();
58
+ }
59
+ /** Starts following file pickers; the browser does this when it opens a page, `page.on("filechooser")` and `waitForFileChooser` when needed */
60
+ startFileChooserTracking() {
61
+ return this.transfers.startFileChooserTracking();
62
+ }
63
+ /**
64
+ * Resolves with the next download this page starts (when it starts, not when it ends: use `download.path()` for that).
65
+ * Call it before the download is triggered, or pass `trigger`, which runs once the wait is set up.
66
+ * @example
67
+ * const download = await page.waitForDownload({ trigger: () => link.click() });
68
+ * const file = await download.path();
69
+ */
70
+ async waitForDownload(options = {}) {
71
+ this.assertOpen();
72
+ return this.transfers.waitForDownload(options);
73
+ }
74
+ /**
75
+ * Resolves with the next file picker the page opens (a click on an `<input type=file>` or its label). Nothing shows
76
+ * on screen: answer it with `chooser.setFiles(...)`.
77
+ * @example
78
+ * const chooser = await page.waitForFileChooser({ trigger: () => page.locator("label[@for='avatar']").click() });
79
+ * await chooser.setFiles("./fixtures/avatar.png");
80
+ */
81
+ async waitForFileChooser(options = {}) {
82
+ this.assertOpen();
83
+ return this.transfers.waitForFileChooser(options);
84
+ }
85
+ /**
86
+ * Playwright-style wait for one page event: `download`, `filechooser`, `request` or `response`. Same as the named
87
+ * `waitFor…` method, with an optional predicate (a function, or `predicate` in the options). Call it before the event
88
+ * is caused, or pass `trigger`, which runs once the wait is set up.
89
+ * @example
90
+ * const chooser = await page.waitForEvent("filechooser", { trigger: () => page.getByText("Upload file").click() });
91
+ * await chooser.setFiles("./myfile.pdf");
92
+ */
93
+ async waitForEvent(event, optionsOrPredicate = {}) {
94
+ return waitForEventOn({
95
+ request: (accepts, options) => this.waitForRequest(accepts, options),
96
+ response: (accepts, options) => this.waitForResponse(accepts, options),
97
+ download: (options, accepts) => {
98
+ this.assertOpen();
99
+ return this.transfers.waitForDownload(options, accepts);
100
+ },
101
+ filechooser: (options, accepts) => {
102
+ this.assertOpen();
103
+ return this.transfers.waitForFileChooser(options, accepts);
104
+ },
105
+ }, event, optionsOrPredicate);
106
+ }
107
+ /**
108
+ * Starts keeping response bodies, which is what makes `response.body()` possible: the browser only keeps a body
109
+ * when this ran before the request started. `page.on("response")` and `waitForResponse` call it; config
110
+ * `network.collectBodies` calls it when the browser opens the page.
111
+ */
112
+ startBodyCollection() {
113
+ return this.net.startBodyCollection();
114
+ }
115
+ /**
116
+ * Resolves with the next response of this page whose URL matches (a pattern, see `route`) or that the function
117
+ * accepts. Call it before the request is made, or pass `trigger`, which runs once the wait is set up.
118
+ * @example
119
+ * const response = await page.waitForResponse("**\/api/users", { trigger: () => button.click() });
120
+ * const users = await response.json();
121
+ */
122
+ waitForResponse(match, options = {}) {
123
+ return this.net.waitForResponse(match, options);
124
+ }
125
+ /** Like `waitForResponse`, for requests */
126
+ waitForRequest(match, options = {}) {
127
+ return this.net.waitForRequest(match, options);
128
+ }
129
+ /** Turns the browser's HTTP cache off (`true`) or back on for this page: every request then goes to the server */
130
+ setCacheDisabled(disabled) {
131
+ return this.net.setCacheDisabled(disabled);
132
+ }
133
+ /**
134
+ * Sends requests whose URL matches to `handler`, which must answer with `route.fulfill()`, `route.continue()`,
135
+ * `route.abort()` or `route.fallback()` (await the answer: a handler that returns first lets the request continue).
136
+ * A string without `*` is an exact URL, with `*` / `**` a glob (`*` stops at `/`), a RegExp is tested on the URL.
137
+ * Routes added later are asked first. Errors a handler throws fail the request and, in the runner, the test.
138
+ * @example
139
+ * await page.route("**\/api/users", (route) => route.fulfill({ json: [{ id: 1 }] }));
140
+ * await page.route(/\.(png|jpg)$/, (route) => route.abort());
141
+ */
142
+ async route(url, handler) {
143
+ this.assertOpen();
144
+ await this.net.route(url, handler);
145
+ }
146
+ /** Removes the routes for `url` (only the one with `handler`, when given) */
147
+ unroute(url, handler) {
148
+ return this.net.unroute(url, handler);
149
+ }
150
+ unrouteAll() {
151
+ return this.net.unrouteAll();
152
+ }
153
+ /** Errors thrown by route handlers so far */
154
+ routeErrors() {
155
+ return this.net.routeErrors();
156
+ }
157
+ /**
158
+ * Starts collecting the page's console output and uncaught exceptions. The browser does this when it opens a page so
159
+ * early output is kept; `page.on("console" | "pageerror")` starts it when needed.
160
+ */
161
+ async startLogging() {
162
+ await this.logging.start();
163
+ }
164
+ /** The newest console and error entries of this page (empty until logging started), oldest first */
165
+ getLogs() {
166
+ return this.logging.entries();
167
+ }
168
+ /** Uncaught exceptions of the page since logging started or `clearLogs()` */
169
+ pageErrors() {
170
+ return this.logging.errors();
171
+ }
172
+ /** How many entries fell out of the page's log buffer */
173
+ get logsDropped() {
174
+ return this.logging.dropped;
175
+ }
176
+ clearLogs() {
177
+ this.logging.clear();
178
+ }
179
+ /** Resolves once everything the page logged before the call has arrived: events and replies share one ordered connection */
180
+ async syncLogs() {
181
+ await this.biDiConnector.send("session.status", {});
182
+ }
183
+ /** Tells the runner not to fail this test on uncaught page exceptions (config `logs.failOnPageError`) */
184
+ allowPageErrors() {
185
+ this.logging.allowErrors();
186
+ }
187
+ get pageErrorsAllowed() {
188
+ return this.logging.errorsAreAllowed;
189
+ }
190
+ /** Starts watching for dialogs; the browser does this when it opens a page, `page.on("dialog")` when needed */
191
+ startDialogHandling() {
192
+ return this.dialogs.start();
193
+ }
194
+ }