playwright-core 1.55.0-alpha-2025-07-29 → 1.55.0-alpha-1753913825000
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/browsers.json +8 -8
- package/lib/generated/injectedScriptSource.js +1 -1
- package/lib/generated/pollingRecorderSource.js +1 -1
- package/lib/protocol/validator.js +0 -16
- package/lib/server/android/android.js +54 -43
- package/lib/server/bidi/bidiPage.js +4 -0
- package/lib/server/browser.js +22 -18
- package/lib/server/browserContext.js +73 -46
- package/lib/server/browserType.js +67 -48
- package/lib/server/chromium/chromium.js +34 -28
- package/lib/server/chromium/crCoverage.js +3 -4
- package/lib/server/chromium/crDragDrop.js +17 -13
- package/lib/server/chromium/videoRecorder.js +10 -19
- package/lib/server/codegen/jsonl.js +2 -1
- package/lib/server/debugController.js +2 -18
- package/lib/server/deviceDescriptorsSource.json +2 -2
- package/lib/server/dispatchers/debugControllerDispatcher.js +0 -9
- package/lib/server/dispatchers/localUtilsDispatcher.js +0 -5
- package/lib/server/electron/electron.js +66 -66
- package/lib/server/fetch.js +6 -2
- package/lib/server/frames.js +6 -6
- package/lib/server/helper.js +4 -7
- package/lib/server/localUtils.js +23 -16
- package/lib/server/playwright.js +0 -4
- package/lib/server/progress.js +17 -11
- package/lib/server/recorder/recorderUtils.js +5 -0
- package/lib/server/recorder.js +61 -1
- package/lib/server/screenshotter.js +72 -56
- package/lib/server/socksClientCertificatesInterceptor.js +59 -43
- package/lib/server/transport.js +19 -15
- package/lib/server/utils/network.js +28 -17
- package/lib/server/utils/processLauncher.js +4 -1
- package/lib/utils/isomorphic/ariaSnapshot.js +58 -4
- package/lib/utils/isomorphic/protocolMetainfo.js +1 -5
- package/lib/utils/isomorphic/urlMatch.js +0 -2
- package/lib/vite/htmlReport/index.html +16 -16
- package/lib/vite/recorder/assets/{codeMirrorModule-cgtwtYOb.js → codeMirrorModule-B5Ye5Cij.js} +1 -1
- package/lib/vite/recorder/assets/{index-KWWSfrzB.js → index-B5VmX9JT.js} +48 -48
- package/lib/vite/recorder/index.html +1 -1
- package/lib/vite/traceViewer/assets/{codeMirrorModule-k_7BpzGX.js → codeMirrorModule-3XE5WU2G.js} +1 -1
- package/lib/vite/traceViewer/assets/{defaultSettingsView-CKM53V_K.js → defaultSettingsView-u5uZPktL.js} +103 -103
- package/lib/vite/traceViewer/{index.CCXGYfh2.js → index.CycYDQ3P.js} +1 -1
- package/lib/vite/traceViewer/index.html +2 -2
- package/lib/vite/traceViewer/{uiMode.CNa3x5Xj.js → uiMode.DeaA8YiP.js} +1 -1
- package/lib/vite/traceViewer/uiMode.html +2 -2
- package/package.json +1 -1
|
@@ -52,12 +52,6 @@ class DebugControllerDispatcher extends import_dispatcher.Dispatcher {
|
|
|
52
52
|
async setReportStateChanged(params, progress) {
|
|
53
53
|
this._object.setReportStateChanged(params.enabled);
|
|
54
54
|
}
|
|
55
|
-
async resetForReuse(params, progress) {
|
|
56
|
-
await this._object.resetForReuse(progress);
|
|
57
|
-
}
|
|
58
|
-
async navigate(params, progress) {
|
|
59
|
-
await this._object.navigate(progress, params.url);
|
|
60
|
-
}
|
|
61
55
|
async setRecorderMode(params, progress) {
|
|
62
56
|
await this._object.setRecorderMode(progress, params);
|
|
63
57
|
}
|
|
@@ -73,9 +67,6 @@ class DebugControllerDispatcher extends import_dispatcher.Dispatcher {
|
|
|
73
67
|
async kill(params, progress) {
|
|
74
68
|
this._object.kill();
|
|
75
69
|
}
|
|
76
|
-
async closeAllBrowsers(params, progress) {
|
|
77
|
-
await this._object.closeAllBrowsers();
|
|
78
|
-
}
|
|
79
70
|
_onDispose() {
|
|
80
71
|
import_utils.eventsHelper.removeEventListeners(this._listeners);
|
|
81
72
|
this._object.dispose();
|
|
@@ -40,7 +40,6 @@ var import_jsonPipeDispatcher = require("../dispatchers/jsonPipeDispatcher");
|
|
|
40
40
|
var import_socksInterceptor = require("../socksInterceptor");
|
|
41
41
|
var import_transport = require("../transport");
|
|
42
42
|
var import_network = require("../utils/network");
|
|
43
|
-
var import_urlMatch = require("../../utils/isomorphic/urlMatch");
|
|
44
43
|
class LocalUtilsDispatcher extends import_dispatcher.Dispatcher {
|
|
45
44
|
constructor(scope, playwright) {
|
|
46
45
|
const localUtils2 = new import_instrumentation.SdkObject(playwright, "localUtils", "localUtils");
|
|
@@ -112,10 +111,6 @@ class LocalUtilsDispatcher extends import_dispatcher.Dispatcher {
|
|
|
112
111
|
pipe.on("close", () => transport.close());
|
|
113
112
|
return { pipe, headers: transport.headers };
|
|
114
113
|
}
|
|
115
|
-
async globToRegex(params, progress) {
|
|
116
|
-
const regex = (0, import_urlMatch.resolveGlobToRegexPattern)(params.baseURL, params.glob, params.webSocketUrl);
|
|
117
|
-
return { regex };
|
|
118
|
-
}
|
|
119
114
|
}
|
|
120
115
|
async function urlToWSEndpoint(progress, endpointURL) {
|
|
121
116
|
if (endpointURL.startsWith("ws"))
|
|
@@ -136,7 +136,6 @@ class Electron extends import_instrumentation.SdkObject {
|
|
|
136
136
|
electronArguments.unshift("--no-sandbox");
|
|
137
137
|
}
|
|
138
138
|
const artifactsDir = await progress.race(import_fs.default.promises.mkdtemp(ARTIFACTS_FOLDER));
|
|
139
|
-
progress.cleanupWhenAborted(() => (0, import_utils.removeFolders)([artifactsDir]));
|
|
140
139
|
const browserLogsCollector = new import_debugLogger.RecentLogsCollector();
|
|
141
140
|
const env = options.env ? (0, import_processLauncher.envArrayToObject)(options.env) : process.env;
|
|
142
141
|
let command;
|
|
@@ -195,73 +194,74 @@ class Electron extends import_instrumentation.SdkObject {
|
|
|
195
194
|
const nodeMatchPromise = waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/);
|
|
196
195
|
const chromeMatchPromise = waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/);
|
|
197
196
|
const debuggerDisconnectPromise = waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
197
|
+
try {
|
|
198
|
+
const nodeMatch = await nodeMatchPromise;
|
|
199
|
+
const nodeTransport = await import_transport.WebSocketTransport.connect(progress, nodeMatch[1]);
|
|
200
|
+
const nodeConnection = new import_crConnection.CRConnection(this, nodeTransport, import_helper.helper.debugProtocolLogger(), browserLogsCollector);
|
|
201
|
+
debuggerDisconnectPromise.then(() => {
|
|
202
|
+
nodeTransport.close();
|
|
203
|
+
}).catch(() => {
|
|
204
|
+
});
|
|
205
|
+
const chromeMatch = await Promise.race([
|
|
206
|
+
chromeMatchPromise,
|
|
207
|
+
waitForXserverError
|
|
208
|
+
]);
|
|
209
|
+
const chromeTransport = await import_transport.WebSocketTransport.connect(progress, chromeMatch[1]);
|
|
210
|
+
const browserProcess = {
|
|
211
|
+
onclose: void 0,
|
|
212
|
+
process: launchedProcess,
|
|
213
|
+
close: gracefullyClose,
|
|
214
|
+
kill
|
|
215
|
+
};
|
|
216
|
+
const contextOptions = {
|
|
217
|
+
...options,
|
|
218
|
+
noDefaultViewport: true
|
|
219
|
+
};
|
|
220
|
+
const browserOptions = {
|
|
221
|
+
name: "electron",
|
|
222
|
+
isChromium: true,
|
|
223
|
+
headful: true,
|
|
224
|
+
persistent: contextOptions,
|
|
225
|
+
browserProcess,
|
|
226
|
+
protocolLogger: import_helper.helper.debugProtocolLogger(),
|
|
227
|
+
browserLogsCollector,
|
|
228
|
+
artifactsDir,
|
|
229
|
+
downloadsPath: artifactsDir,
|
|
230
|
+
tracesDir: options.tracesDir || artifactsDir,
|
|
231
|
+
originalLaunchOptions: {}
|
|
232
|
+
};
|
|
233
|
+
(0, import_browserContext.validateBrowserContextOptions)(contextOptions, browserOptions);
|
|
234
|
+
const browser = await progress.race(import_crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions));
|
|
235
|
+
app = new ElectronApplication(this, browser, nodeConnection, launchedProcess);
|
|
236
|
+
await progress.race(app.initialize());
|
|
237
|
+
return app;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
await kill();
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
240
242
|
}
|
|
241
243
|
}
|
|
242
|
-
function waitForLine(progress, process2, regex) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
}));
|
|
244
|
+
async function waitForLine(progress, process2, regex) {
|
|
245
|
+
const promise = new import_utils.ManualPromise();
|
|
246
|
+
const rl = readline.createInterface({ input: process2.stderr });
|
|
247
|
+
const failError = new Error("Process failed to launch!");
|
|
248
|
+
const listeners = [
|
|
249
|
+
import_eventsHelper.eventsHelper.addEventListener(rl, "line", onLine),
|
|
250
|
+
import_eventsHelper.eventsHelper.addEventListener(rl, "close", () => promise.reject(failError)),
|
|
251
|
+
import_eventsHelper.eventsHelper.addEventListener(process2, "exit", () => promise.reject(failError)),
|
|
252
|
+
// It is Ok to remove error handler because we did not create process and there is another listener.
|
|
253
|
+
import_eventsHelper.eventsHelper.addEventListener(process2, "error", () => promise.reject(failError))
|
|
254
|
+
];
|
|
255
|
+
function onLine(line) {
|
|
256
|
+
const match = line.match(regex);
|
|
257
|
+
if (match)
|
|
258
|
+
promise.resolve(match);
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
return await progress.race(promise);
|
|
262
|
+
} finally {
|
|
263
|
+
import_eventsHelper.eventsHelper.removeEventListeners(listeners);
|
|
264
|
+
}
|
|
265
265
|
}
|
|
266
266
|
// Annotate the CommonJS export names for ESM import in node:
|
|
267
267
|
0 && (module.exports = {
|
package/lib/server/fetch.js
CHANGED
|
@@ -221,6 +221,7 @@ ${text}`;
|
|
|
221
221
|
postData
|
|
222
222
|
};
|
|
223
223
|
this.emit(APIRequestContext.Events.Request, requestEvent);
|
|
224
|
+
let destroyRequest;
|
|
224
225
|
const resultPromise = new Promise((fulfill, reject) => {
|
|
225
226
|
const requestConstructor = (url.protocol === "https:" ? import_https.default : import_http.default).request;
|
|
226
227
|
const agent = options.agent || (url.protocol === "https:" ? import_happyEyeballs.httpsHappyEyeballsAgent : import_happyEyeballs.httpHappyEyeballsAgent);
|
|
@@ -379,7 +380,7 @@ ${text}`;
|
|
|
379
380
|
body.on("end", notifyBodyFinished);
|
|
380
381
|
});
|
|
381
382
|
request.on("error", reject);
|
|
382
|
-
|
|
383
|
+
destroyRequest = () => request.destroy();
|
|
383
384
|
listeners.push(
|
|
384
385
|
import_utils.eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => {
|
|
385
386
|
reject(new Error("Request context disposed."));
|
|
@@ -431,7 +432,10 @@ ${text}`;
|
|
|
431
432
|
request.write(postData);
|
|
432
433
|
request.end();
|
|
433
434
|
});
|
|
434
|
-
return progress.race(resultPromise)
|
|
435
|
+
return progress.race(resultPromise).catch((error) => {
|
|
436
|
+
destroyRequest?.();
|
|
437
|
+
throw error;
|
|
438
|
+
});
|
|
435
439
|
}
|
|
436
440
|
_getHttpCredentials(url) {
|
|
437
441
|
if (!this._defaultOptions().httpCredentials?.origin || url.origin.toLowerCase() === this._defaultOptions().httpCredentials?.origin?.toLowerCase())
|
package/lib/server/frames.js
CHANGED
|
@@ -618,7 +618,7 @@ class Frame extends import_instrumentation.SdkObject {
|
|
|
618
618
|
return null;
|
|
619
619
|
return continuePolling;
|
|
620
620
|
}
|
|
621
|
-
const result = await progress.
|
|
621
|
+
const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, root }) => {
|
|
622
622
|
if (root && !root.isConnected)
|
|
623
623
|
throw injected.createStacklessError("Element is not attached to the DOM");
|
|
624
624
|
const elements = injected.querySelectorAll(info.parsed, root || document);
|
|
@@ -633,7 +633,7 @@ class Frame extends import_instrumentation.SdkObject {
|
|
|
633
633
|
log2 = ` locator resolved to ${visible2 ? "visible" : "hidden"} ${injected.previewNode(element2)}`;
|
|
634
634
|
}
|
|
635
635
|
return { log: log2, element: element2, visible: visible2, attached: !!element2 };
|
|
636
|
-
}, { info: resolved.info, root: resolved.frame === this ? scope : void 0 })
|
|
636
|
+
}, { info: resolved.info, root: resolved.frame === this ? scope : void 0 }));
|
|
637
637
|
const { log, visible, attached } = await progress.race(result.evaluate((r) => ({ log: r.log, visible: r.visible, attached: r.attached })));
|
|
638
638
|
if (log)
|
|
639
639
|
progress.log(log);
|
|
@@ -898,7 +898,7 @@ class Frame extends import_instrumentation.SdkObject {
|
|
|
898
898
|
const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, { strict }));
|
|
899
899
|
if (!resolved)
|
|
900
900
|
return continuePolling;
|
|
901
|
-
const result = await progress.
|
|
901
|
+
const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, callId }) => {
|
|
902
902
|
const elements = injected.querySelectorAll(info.parsed, document);
|
|
903
903
|
if (callId)
|
|
904
904
|
injected.markTargetElements(new Set(elements), callId);
|
|
@@ -912,7 +912,7 @@ class Frame extends import_instrumentation.SdkObject {
|
|
|
912
912
|
log2 = ` locator resolved to ${injected.previewNode(element2)}`;
|
|
913
913
|
}
|
|
914
914
|
return { log: log2, success: !!element2, element: element2 };
|
|
915
|
-
}, { info: resolved.info, callId: progress.metadata.id })
|
|
915
|
+
}, { info: resolved.info, callId: progress.metadata.id }));
|
|
916
916
|
const { log, success } = await progress.race(result.evaluate((r) => ({ log: r.log, success: r.success })));
|
|
917
917
|
if (log)
|
|
918
918
|
progress.log(log);
|
|
@@ -982,8 +982,8 @@ class Frame extends import_instrumentation.SdkObject {
|
|
|
982
982
|
async blur(progress, selector, options) {
|
|
983
983
|
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._blur(progress)));
|
|
984
984
|
}
|
|
985
|
-
async resolveSelector(progress, selector) {
|
|
986
|
-
const element = await progress.race(this.selectors.query(selector));
|
|
985
|
+
async resolveSelector(progress, selector, options = {}) {
|
|
986
|
+
const element = await progress.race(this.selectors.query(selector, options));
|
|
987
987
|
if (!element)
|
|
988
988
|
throw new Error(`No element matching ${selector}`);
|
|
989
989
|
const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => {
|
package/lib/server/helper.js
CHANGED
|
@@ -51,22 +51,19 @@ class Helper {
|
|
|
51
51
|
}
|
|
52
52
|
static waitForEvent(progress, emitter, event, predicate) {
|
|
53
53
|
const listeners = [];
|
|
54
|
-
const
|
|
54
|
+
const dispose = () => import_eventsHelper.eventsHelper.removeEventListeners(listeners);
|
|
55
|
+
const promise = progress.race(new Promise((resolve, reject) => {
|
|
55
56
|
listeners.push(import_eventsHelper.eventsHelper.addEventListener(emitter, event, (eventArg) => {
|
|
56
57
|
try {
|
|
57
58
|
if (predicate && !predicate(eventArg))
|
|
58
59
|
return;
|
|
59
|
-
import_eventsHelper.eventsHelper.removeEventListeners(listeners);
|
|
60
60
|
resolve(eventArg);
|
|
61
61
|
} catch (e) {
|
|
62
|
-
import_eventsHelper.eventsHelper.removeEventListeners(listeners);
|
|
63
62
|
reject(e);
|
|
64
63
|
}
|
|
65
64
|
}));
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
progress.cleanupWhenAborted(dispose);
|
|
69
|
-
return { promise: progress.race(promise), dispose };
|
|
65
|
+
})).finally(() => dispose());
|
|
66
|
+
return { promise, dispose };
|
|
70
67
|
}
|
|
71
68
|
static secondsToRoundishMillis(value) {
|
|
72
69
|
return (value * 1e6 | 0) / 1e3;
|
package/lib/server/localUtils.js
CHANGED
|
@@ -135,13 +135,18 @@ async function harOpen(progress, harBackends, params) {
|
|
|
135
135
|
let harBackend;
|
|
136
136
|
if (params.file.endsWith(".zip")) {
|
|
137
137
|
const zipFile = new import_zipFile.ZipFile(params.file);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
try {
|
|
139
|
+
const entryNames = await progress.race(zipFile.entries());
|
|
140
|
+
const harEntryName = entryNames.find((e) => e.endsWith(".har"));
|
|
141
|
+
if (!harEntryName)
|
|
142
|
+
return { error: "Specified archive does not have a .har file" };
|
|
143
|
+
const har = await progress.race(zipFile.read(harEntryName));
|
|
144
|
+
const harFile = JSON.parse(har.toString());
|
|
145
|
+
harBackend = new import_harBackend.HarBackend(harFile, null, zipFile);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
zipFile.close();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
145
150
|
} else {
|
|
146
151
|
const harFile = JSON.parse(await progress.race(import_fs.default.promises.readFile(params.file, "utf-8")));
|
|
147
152
|
harBackend = new import_harBackend.HarBackend(harFile, import_path.default.dirname(params.file), null);
|
|
@@ -165,16 +170,18 @@ function harClose(harBackends, params) {
|
|
|
165
170
|
async function harUnzip(progress, params) {
|
|
166
171
|
const dir = import_path.default.dirname(params.zipFile);
|
|
167
172
|
const zipFile = new import_zipFile.ZipFile(params.zipFile);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
try {
|
|
174
|
+
for (const entry of await progress.race(zipFile.entries())) {
|
|
175
|
+
const buffer = await progress.race(zipFile.read(entry));
|
|
176
|
+
if (entry === "har.har")
|
|
177
|
+
await progress.race(import_fs.default.promises.writeFile(params.harFile, buffer));
|
|
178
|
+
else
|
|
179
|
+
await progress.race(import_fs.default.promises.writeFile(import_path.default.join(dir, entry), buffer));
|
|
180
|
+
}
|
|
181
|
+
await progress.race(import_fs.default.promises.unlink(params.zipFile));
|
|
182
|
+
} finally {
|
|
183
|
+
zipFile.close();
|
|
175
184
|
}
|
|
176
|
-
await progress.race(import_fs.default.promises.unlink(params.zipFile));
|
|
177
|
-
zipFile.close();
|
|
178
185
|
}
|
|
179
186
|
async function tracingStarted(progress, stackSessions, params) {
|
|
180
187
|
let tmpDir = void 0;
|
package/lib/server/playwright.js
CHANGED
|
@@ -58,10 +58,6 @@ class Playwright extends import_instrumentation.SdkObject {
|
|
|
58
58
|
this.android = new import_android.Android(this, new import_backendAdb.AdbBackend());
|
|
59
59
|
this.debugController = new import_debugController.DebugController(this);
|
|
60
60
|
}
|
|
61
|
-
async hideHighlight() {
|
|
62
|
-
await Promise.all([...this._allPages].map((p) => p.hideHighlight().catch(() => {
|
|
63
|
-
})));
|
|
64
|
-
}
|
|
65
61
|
allBrowsers() {
|
|
66
62
|
return [...this._allBrowsers];
|
|
67
63
|
}
|
package/lib/server/progress.js
CHANGED
|
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var progress_exports = {};
|
|
20
20
|
__export(progress_exports, {
|
|
21
21
|
ProgressController: () => ProgressController,
|
|
22
|
-
isAbortError: () => isAbortError
|
|
22
|
+
isAbortError: () => isAbortError,
|
|
23
|
+
raceUncancellableOperationWithCleanup: () => raceUncancellableOperationWithCleanup
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(progress_exports);
|
|
25
26
|
var import_errors = require("./errors");
|
|
@@ -67,15 +68,6 @@ class ProgressController {
|
|
|
67
68
|
const promises = Array.isArray(promise) ? promise : [promise];
|
|
68
69
|
return Promise.race([...promises, this._forceAbortPromise]);
|
|
69
70
|
},
|
|
70
|
-
raceWithCleanup: (promise, cleanup) => {
|
|
71
|
-
return progress.race(promise.then((result) => {
|
|
72
|
-
if (this._state !== "running")
|
|
73
|
-
cleanup(result);
|
|
74
|
-
else
|
|
75
|
-
this._cleanups.push(() => cleanup(result));
|
|
76
|
-
return result;
|
|
77
|
-
}));
|
|
78
|
-
},
|
|
79
71
|
wait: async (timeout2) => {
|
|
80
72
|
let timer2;
|
|
81
73
|
const promise = new Promise((f) => timer2 = setTimeout(f, timeout2));
|
|
@@ -117,8 +109,22 @@ const kAbortErrorSymbol = Symbol("kAbortError");
|
|
|
117
109
|
function isAbortError(error) {
|
|
118
110
|
return !!error[kAbortErrorSymbol];
|
|
119
111
|
}
|
|
112
|
+
async function raceUncancellableOperationWithCleanup(progress, run, cleanup) {
|
|
113
|
+
let aborted = false;
|
|
114
|
+
try {
|
|
115
|
+
return await progress.race(run().then(async (t) => {
|
|
116
|
+
if (aborted)
|
|
117
|
+
await cleanup(t);
|
|
118
|
+
return t;
|
|
119
|
+
}));
|
|
120
|
+
} catch (error) {
|
|
121
|
+
aborted = true;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
120
125
|
// Annotate the CommonJS export names for ESM import in node:
|
|
121
126
|
0 && (module.exports = {
|
|
122
127
|
ProgressController,
|
|
123
|
-
isAbortError
|
|
128
|
+
isAbortError,
|
|
129
|
+
raceUncancellableOperationWithCleanup
|
|
124
130
|
});
|
|
@@ -22,6 +22,7 @@ __export(recorderUtils_exports, {
|
|
|
22
22
|
collapseActions: () => collapseActions,
|
|
23
23
|
frameForAction: () => frameForAction,
|
|
24
24
|
generateFrameSelector: () => generateFrameSelector,
|
|
25
|
+
isAssertAction: () => isAssertAction,
|
|
25
26
|
mainFrameForAction: () => mainFrameForAction,
|
|
26
27
|
metadataToCallLog: () => metadataToCallLog,
|
|
27
28
|
shouldMergeAction: () => shouldMergeAction
|
|
@@ -75,6 +76,9 @@ async function frameForAction(pageAliases, actionInContext, action) {
|
|
|
75
76
|
throw new Error("Internal error: frame not found");
|
|
76
77
|
return result.frame;
|
|
77
78
|
}
|
|
79
|
+
function isAssertAction(action) {
|
|
80
|
+
return action.name.startsWith("assert");
|
|
81
|
+
}
|
|
78
82
|
function isSameAction(a, b) {
|
|
79
83
|
return a.action.name === b.action.name && a.frame.pageAlias === b.frame.pageAlias && a.frame.framePath.join("|") === b.frame.framePath.join("|");
|
|
80
84
|
}
|
|
@@ -151,6 +155,7 @@ async function generateFrameSelectorInParent(parent, frame) {
|
|
|
151
155
|
collapseActions,
|
|
152
156
|
frameForAction,
|
|
153
157
|
generateFrameSelector,
|
|
158
|
+
isAssertAction,
|
|
154
159
|
mainFrameForAction,
|
|
155
160
|
metadataToCallLog,
|
|
156
161
|
shouldMergeAction
|
package/lib/server/recorder.js
CHANGED
|
@@ -48,6 +48,8 @@ var import_utils2 = require("./../utils");
|
|
|
48
48
|
var import_frames = require("./frames");
|
|
49
49
|
var import_page = require("./page");
|
|
50
50
|
var import_recorderRunner = require("./recorder/recorderRunner");
|
|
51
|
+
var import_ariaSnapshot = require("../utils/isomorphic/ariaSnapshot");
|
|
52
|
+
var import_utilsBundle = require("../utilsBundle");
|
|
51
53
|
const recorderSymbol = Symbol("recorderSymbol");
|
|
52
54
|
const RecorderEvent = {
|
|
53
55
|
PausedStateChanged: "pausedStateChanged",
|
|
@@ -440,15 +442,66 @@ class Recorder extends import_events.default {
|
|
|
440
442
|
};
|
|
441
443
|
return actionInContext;
|
|
442
444
|
}
|
|
445
|
+
async _maybeGenerateAssertAction(frame, actionInContext) {
|
|
446
|
+
const lastAction = getLastFrameAction(frame);
|
|
447
|
+
if ((0, import_recorderUtils.isAssertAction)(actionInContext.action))
|
|
448
|
+
return;
|
|
449
|
+
if (lastAction && ((0, import_recorderUtils.isAssertAction)(lastAction.action) || (0, import_recorderUtils.shouldMergeAction)(actionInContext, lastAction)))
|
|
450
|
+
return;
|
|
451
|
+
const newSnapshot = actionInContext.action.ariaSnapshot;
|
|
452
|
+
if (!newSnapshot)
|
|
453
|
+
return;
|
|
454
|
+
const lastSnapshot = lastAction?.action.ariaSnapshot || `- document [ref=e1]
|
|
455
|
+
`;
|
|
456
|
+
if (!lastSnapshot)
|
|
457
|
+
return;
|
|
458
|
+
const callMetadata = (0, import_instrumentation.serverSideCallMetadata)();
|
|
459
|
+
const controller = new import_progress.ProgressController(callMetadata, frame);
|
|
460
|
+
const selector = await controller.run(async (progress) => {
|
|
461
|
+
const ref = (0, import_ariaSnapshot.findNewElementRef)(import_utilsBundle.yaml, lastSnapshot, newSnapshot);
|
|
462
|
+
if (!ref)
|
|
463
|
+
return;
|
|
464
|
+
const { resolvedSelector } = await frame.resolveSelector(progress, `aria-ref=${ref}`, { mainWorld: true }).catch(() => ({ resolvedSelector: void 0 }));
|
|
465
|
+
if (!resolvedSelector)
|
|
466
|
+
return;
|
|
467
|
+
const isVisible = await frame._page.mainFrame().isVisible(progress, resolvedSelector, { strict: true }).catch(() => false);
|
|
468
|
+
return isVisible ? resolvedSelector : void 0;
|
|
469
|
+
}).catch(() => void 0);
|
|
470
|
+
if (!selector)
|
|
471
|
+
return;
|
|
472
|
+
if (!actionInContext.frame.framePath.length && "selector" in actionInContext.action && actionInContext.action.selector === selector)
|
|
473
|
+
return;
|
|
474
|
+
const assertActionInContext = {
|
|
475
|
+
frame: {
|
|
476
|
+
pageGuid: actionInContext.frame.pageGuid,
|
|
477
|
+
pageAlias: actionInContext.frame.pageAlias,
|
|
478
|
+
framePath: []
|
|
479
|
+
},
|
|
480
|
+
action: {
|
|
481
|
+
name: "assertVisible",
|
|
482
|
+
selector,
|
|
483
|
+
signals: []
|
|
484
|
+
},
|
|
485
|
+
startTime: actionInContext.startTime,
|
|
486
|
+
endTime: actionInContext.startTime
|
|
487
|
+
};
|
|
488
|
+
return assertActionInContext;
|
|
489
|
+
}
|
|
443
490
|
async _performAction(frame, action) {
|
|
444
491
|
const actionInContext = await this._createActionInContext(frame, action);
|
|
492
|
+
const assertActionInContext = await this._maybeGenerateAssertAction(frame, actionInContext);
|
|
493
|
+
if (assertActionInContext)
|
|
494
|
+
this._signalProcessor.addAction(assertActionInContext);
|
|
445
495
|
this._signalProcessor.addAction(actionInContext);
|
|
496
|
+
setLastFrameAction(frame, actionInContext);
|
|
446
497
|
if (actionInContext.action.name !== "openPage" && actionInContext.action.name !== "closePage")
|
|
447
498
|
await (0, import_recorderRunner.performAction)(this._pageAliases, actionInContext);
|
|
448
499
|
actionInContext.endTime = (0, import_utils2.monotonicTime)();
|
|
449
500
|
}
|
|
450
501
|
async _recordAction(frame, action) {
|
|
451
|
-
|
|
502
|
+
const actionInContext = await this._createActionInContext(frame, action);
|
|
503
|
+
this._signalProcessor.addAction(actionInContext);
|
|
504
|
+
setLastFrameAction(frame, actionInContext);
|
|
452
505
|
}
|
|
453
506
|
_onFrameNavigated(frame, page) {
|
|
454
507
|
const pageAlias = this._pageAliases.get(page);
|
|
@@ -482,6 +535,13 @@ function languageForFile(file) {
|
|
|
482
535
|
return "csharp";
|
|
483
536
|
return "javascript";
|
|
484
537
|
}
|
|
538
|
+
const kLastFrameActionSymbol = Symbol("lastFrameAction");
|
|
539
|
+
function getLastFrameAction(frame) {
|
|
540
|
+
return frame[kLastFrameActionSymbol];
|
|
541
|
+
}
|
|
542
|
+
function setLastFrameAction(frame, action) {
|
|
543
|
+
frame[kLastFrameActionSymbol] = action;
|
|
544
|
+
}
|
|
485
545
|
// Annotate the CommonJS export names for ESM import in node:
|
|
486
546
|
0 && (module.exports = {
|
|
487
547
|
Recorder,
|