flashwright 1.64.1-flashwright.1 → 1.64.1-flashwright.2
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 +2 -0
- package/cli.js +11 -13
- package/lib/cli/testActions.js +2 -1
- package/lib/index.js +3 -0
- package/lib/runner/index.js +5 -1
- package/lib/runner/index.js.txt +4 -3
- package/lib/runner/session.js +723 -0
- package/lib/runner/session.js.txt +8 -0
- package/lib/runner/workerStandby.js +11 -2
- package/lib/runner/workerStandby.js.txt +4 -3
- package/lib/speculativeBrowser.js +102 -2
- package/lib/worker/workerProcessEntry.js +102 -2
- package/lib/worker/workerProcessEntry.js.txt +3 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -13,4 +13,6 @@ npx playwright install chromium
|
|
|
13
13
|
|
|
14
14
|
Without the test runner, `playwright` is `flashwright` and `playwright-core` is `flashwright-core`. See the [project page](https://github.com/xcodebeast/flashwright) for benchmarks, speed options and the differences from Playwright.
|
|
15
15
|
|
|
16
|
+
Re-running tests, like an agent checking its work? Outside CI, a warm session keeps the browser between runs, so one test re-runs in about 0.1 s instead of 0.7 s with Playwright ([warm session](https://github.com/xcodebeast/flashwright#warm-session); `FLASHWRIGHT_SESSION=0` turns it off).
|
|
17
|
+
|
|
16
18
|
Flashwright is an independent fork and is not affiliated with Microsoft. Apache-2.0, like Playwright.
|
package/cli.js
CHANGED
|
@@ -24,16 +24,14 @@ if (nodeModule.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
|
24
24
|
process.env.NODE_COMPILE_CACHE = directory;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// Flashwright:
|
|
28
|
-
//
|
|
29
|
-
require('./lib/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
require('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
program.parse(process.argv);
|
|
39
|
-
}
|
|
27
|
+
// Flashwright: runs the command here, or in a runner process that the warm session prepared
|
|
28
|
+
// ahead of time (see session.ts). `cli` commands only forward to a browser session.
|
|
29
|
+
require('./lib/runner/session').runCli(process.argv, {
|
|
30
|
+
// Compiles Playwright's bundles with code caches that include their lazily compiled functions.
|
|
31
|
+
installCodeCache: role => require('./lib/codeCache').installCodeCache(role),
|
|
32
|
+
// Starts the first test workers while the CLI loads.
|
|
33
|
+
prespawnWorkers: argv => require('./lib/runner/workerStandby').prespawnWorkerForTestCommand(argv),
|
|
34
|
+
retargetWorkers: argv => require('./lib/runner/workerStandby').retargetStandbys(argv),
|
|
35
|
+
loadProgram: () => require('./lib/program').program,
|
|
36
|
+
runCliClient: () => require('playwright-core/lib/coreBundle').runCliClient(),
|
|
37
|
+
});
|
package/lib/cli/testActions.js
CHANGED
|
@@ -37,6 +37,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
37
37
|
var import_path = __toESM(require("path"));
|
|
38
38
|
var import_common = require("../common");
|
|
39
39
|
var import_runner = require("../runner");
|
|
40
|
+
var import_session = require("../runner/session");
|
|
40
41
|
const { gracefullyProcessExitDoNotHang } = require("playwright-core/lib/coreBundle").utils;
|
|
41
42
|
async function runTests(args, opts) {
|
|
42
43
|
const cliOverrides = overridesFromOptions(opts);
|
|
@@ -90,7 +91,7 @@ async function runTests(args, opts) {
|
|
|
90
91
|
}
|
|
91
92
|
const status = await import_runner.testRunner.runAllTestsWithConfig(config, options);
|
|
92
93
|
const exitCode = status === "interrupted" ? 130 : status === "passed" ? 0 : 1;
|
|
93
|
-
gracefullyProcessExitDoNotHang(exitCode);
|
|
94
|
+
gracefullyProcessExitDoNotHang(exitCode, () => (0, import_session.flushSessionBrowsers)(exitCode));
|
|
94
95
|
}
|
|
95
96
|
async function runTestServerAction(opts) {
|
|
96
97
|
const host = opts.host;
|
package/lib/index.js
CHANGED
|
@@ -213,6 +213,7 @@ const playwrightFixtures = {
|
|
|
213
213
|
playwright._defaultLaunchOptions = options;
|
|
214
214
|
await use(options);
|
|
215
215
|
playwright._defaultLaunchOptions = void 0;
|
|
216
|
+
await (0, import_speculativeBrowser.returnSpeculativeBrowser)();
|
|
216
217
|
}, { scope: "worker", auto: true, box: true }],
|
|
217
218
|
browser: [async ({ playwright, browserName, _browserOptions, connectOptions, _recycleContext }, use, workerInfo) => {
|
|
218
219
|
if (!["chromium", "firefox", "webkit"].includes(browserName))
|
|
@@ -239,6 +240,8 @@ const playwrightFixtures = {
|
|
|
239
240
|
if (process.env.PLAYWRIGHT_DASHBOARD)
|
|
240
241
|
await browser.bind(`worker-${workerInfo.parallelIndex}`);
|
|
241
242
|
await use(browser);
|
|
243
|
+
if (await (0, import_speculativeBrowser.handBrowsersToSession)(browser, true))
|
|
244
|
+
return;
|
|
242
245
|
if (browser.contexts().length)
|
|
243
246
|
await browser.close({ reason: "Test ended." });
|
|
244
247
|
else
|
package/lib/runner/index.js
CHANGED
|
@@ -1896,6 +1896,7 @@ var import_fs3 = __toESM(require("fs"));
|
|
|
1896
1896
|
var import_child_process = __toESM(require("child_process"));
|
|
1897
1897
|
var import_events = require("events");
|
|
1898
1898
|
var import_workerStandby = require("./workerStandby");
|
|
1899
|
+
var import_session = require("./session");
|
|
1899
1900
|
var debug3 = require("playwright-core/lib/utilsBundle").debug;
|
|
1900
1901
|
var { assert } = require("playwright-core/lib/coreBundle").iso;
|
|
1901
1902
|
var { monotonicTime: monotonicTime3, timeOrigin } = require("playwright-core/lib/coreBundle").iso;
|
|
@@ -1944,7 +1945,10 @@ var ProcessHost = class extends import_events.EventEmitter {
|
|
|
1944
1945
|
});
|
|
1945
1946
|
this.process.on("error", (e) => {
|
|
1946
1947
|
});
|
|
1947
|
-
|
|
1948
|
+
(0, import_session.trackSessionWorker)(this.process);
|
|
1949
|
+
this.process.on("message", (message, handle) => {
|
|
1950
|
+
if ((0, import_session.receiveWorkerMessage)(this.process, message, handle))
|
|
1951
|
+
return;
|
|
1948
1952
|
if (debug3.enabled("pw:test:protocol"))
|
|
1949
1953
|
debug3("pw:test:protocol")("\u25C0 RECV " + JSON.stringify(message));
|
|
1950
1954
|
if (message.method === "__env_produced__") {
|
package/lib/runner/index.js.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# packages/playwright/lib/runner/index.js
|
|
2
|
-
# total: 321.
|
|
2
|
+
# total: 321.4 KB
|
|
3
3
|
|
|
4
4
|
## Inlined (49)
|
|
5
5
|
1.3 KB packages/playwright/src/isomorphic/events.ts
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
1.7 KB packages/playwright/src/runner/lastRun.ts
|
|
36
36
|
1.6 KB packages/playwright/src/runner/loaderHost.ts
|
|
37
37
|
13.3 KB packages/playwright/src/runner/loadUtils.ts
|
|
38
|
-
6.
|
|
38
|
+
6.5 KB packages/playwright/src/runner/processHost.ts
|
|
39
39
|
7.9 KB packages/playwright/src/runner/projectUtils.ts
|
|
40
40
|
5.5 KB packages/playwright/src/runner/rebase.ts
|
|
41
41
|
3.4 KB packages/playwright/src/runner/reporters.ts
|
|
@@ -52,12 +52,13 @@
|
|
|
52
52
|
13.0 KB packages/playwright/src/runner/watchMode.ts
|
|
53
53
|
2.1 KB packages/playwright/src/runner/workerHost.ts
|
|
54
54
|
|
|
55
|
-
## External (
|
|
55
|
+
## External (10)
|
|
56
56
|
../common
|
|
57
57
|
../loader/loaderProcessEntry.js
|
|
58
58
|
../transform/babelBundle
|
|
59
59
|
../util
|
|
60
60
|
../worker/workerProcessEntry.js
|
|
61
|
+
./session
|
|
61
62
|
./workerStandby
|
|
62
63
|
playwright-core
|
|
63
64
|
playwright-core/lib/coreBundle
|
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// packages/playwright/src/runner/session.ts
|
|
31
|
+
var session_exports = {};
|
|
32
|
+
__export(session_exports, {
|
|
33
|
+
flushSessionBrowsers: () => flushSessionBrowsers,
|
|
34
|
+
receiveWorkerMessage: () => receiveWorkerMessage,
|
|
35
|
+
requestSessionBrowser: () => requestSessionBrowser,
|
|
36
|
+
runCli: () => runCli,
|
|
37
|
+
sessionStandbyEnv: () => sessionStandbyEnv,
|
|
38
|
+
trackSessionWorker: () => trackSessionWorker
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(session_exports);
|
|
41
|
+
var import_child_process = __toESM(require("child_process"));
|
|
42
|
+
var import_crypto = __toESM(require("crypto"));
|
|
43
|
+
var import_fs = __toESM(require("fs"));
|
|
44
|
+
var import_inspector = __toESM(require("inspector"));
|
|
45
|
+
var import_net = __toESM(require("net"));
|
|
46
|
+
var import_os = __toESM(require("os"));
|
|
47
|
+
var import_path = __toESM(require("path"));
|
|
48
|
+
var import_url = __toESM(require("url"));
|
|
49
|
+
var kIdleTimeout = (Number(process.env.FLASHWRIGHT_SESSION_TIMEOUT) || 5 * 60) * 1e3;
|
|
50
|
+
var kVolatileEnv = /* @__PURE__ */ new Set(["_", "PWD", "OLDPWD", "SHLVL", "COLUMNS", "LINES"]);
|
|
51
|
+
var kInteractiveArgs = ["--ui", "--ui-host", "--ui-port", "--debug", "--list", "-h", "--help"];
|
|
52
|
+
var kBrowserMessage = "__flashwright_browser__";
|
|
53
|
+
var kBrowserRequest = "__flashwright_browser_request__";
|
|
54
|
+
var kReturnBrowser = "__flashwright_return_browser__";
|
|
55
|
+
var kBrowserReturned = "__flashwright_browser_returned__";
|
|
56
|
+
var kMaxBrowsers = 8;
|
|
57
|
+
function runCli(argv, hooks) {
|
|
58
|
+
if (argv[2] === "cli") {
|
|
59
|
+
hooks.installCodeCache("cli");
|
|
60
|
+
hooks.runCliClient();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (process.env.FLASHWRIGHT_SESSION_ROLE === "warm") {
|
|
64
|
+
runWarmProcess(hooks).catch((error) => {
|
|
65
|
+
process.stderr.write(`Flashwright session: ${error.stack || error}
|
|
66
|
+
`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (argv[2] === "test" && sessionRequested() && !argv.some((arg) => kInteractiveArgs.includes(arg.split("=")[0]))) {
|
|
72
|
+
const request = createRequest(argv);
|
|
73
|
+
runInSession(request).then((outcome) => runLocally(argv, hooks, outcome === "start" ? request : void 0), () => runLocally(argv, hooks));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
runLocally(argv, hooks);
|
|
77
|
+
}
|
|
78
|
+
function runLocally(argv, hooks, startSessionWith) {
|
|
79
|
+
hooks.installCodeCache(argv[2] === "test" ? "runner" : "cli");
|
|
80
|
+
if (startSessionWith)
|
|
81
|
+
startSession(startSessionWith);
|
|
82
|
+
if (argv[2] === "test")
|
|
83
|
+
hooks.prespawnWorkers(argv);
|
|
84
|
+
hooks.loadProgram().parse(argv);
|
|
85
|
+
}
|
|
86
|
+
function sessionRequested() {
|
|
87
|
+
if (process.env.PWDEBUG || process.env.PWTEST_WATCH || import_inspector.default.url() || process.platform === "win32")
|
|
88
|
+
return false;
|
|
89
|
+
const value = process.env.FLASHWRIGHT_SESSION;
|
|
90
|
+
if (value)
|
|
91
|
+
return value !== "0" && value !== "false";
|
|
92
|
+
return !process.env.CI;
|
|
93
|
+
}
|
|
94
|
+
function buildStamp() {
|
|
95
|
+
const files = ["program.js", "common/index.js", "runner/index.js", "runner/session.js", "worker/workerProcessEntry.js"].map((file) => import_path.default.join(__dirname, "..", file));
|
|
96
|
+
try {
|
|
97
|
+
files.push(require.resolve("playwright-core/lib/coreBundle"));
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
return files.map((file) => {
|
|
101
|
+
try {
|
|
102
|
+
const stat = import_fs.default.statSync(file);
|
|
103
|
+
return `${file}:${stat.size}:${stat.mtimeMs}`;
|
|
104
|
+
} catch {
|
|
105
|
+
return file;
|
|
106
|
+
}
|
|
107
|
+
}).join("|");
|
|
108
|
+
}
|
|
109
|
+
function terminalProfile() {
|
|
110
|
+
return {
|
|
111
|
+
stdout: process.stdout.isTTY ? `${process.stdout.columns || 0}x${process.stdout.rows || 0}` : "false",
|
|
112
|
+
stdinTTY: !!process.stdin.isTTY
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function stableEnv(env) {
|
|
116
|
+
const result = {};
|
|
117
|
+
for (const [key, value] of Object.entries(env)) {
|
|
118
|
+
if (value !== void 0 && !kVolatileEnv.has(key) && !key.startsWith("FLASHWRIGHT_SESSION_"))
|
|
119
|
+
result[key] = value;
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function createRequest(argv) {
|
|
124
|
+
return {
|
|
125
|
+
type: "run",
|
|
126
|
+
argv,
|
|
127
|
+
cwd: process.cwd(),
|
|
128
|
+
env: stableEnv(process.env),
|
|
129
|
+
execPath: process.execPath,
|
|
130
|
+
execArgv: process.execArgv,
|
|
131
|
+
cliPath: argv[1],
|
|
132
|
+
build: buildStamp(),
|
|
133
|
+
profile: terminalProfile()
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function warmKey(request) {
|
|
137
|
+
const env = Object.entries(stableEnv(request.env)).sort(([a], [b]) => a < b ? -1 : 1);
|
|
138
|
+
return import_crypto.default.createHash("sha1").update(JSON.stringify([env, request.execPath, request.execArgv, request.cliPath, request.build, request.profile])).digest("hex");
|
|
139
|
+
}
|
|
140
|
+
function sessionSocketPath(cwd) {
|
|
141
|
+
const uid = import_os.default.userInfo().uid;
|
|
142
|
+
const directory = import_path.default.join(import_os.default.tmpdir(), `fw-${uid}`);
|
|
143
|
+
try {
|
|
144
|
+
import_fs.default.mkdirSync(directory, { mode: 448 });
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const stat = import_fs.default.statSync(directory);
|
|
149
|
+
if (!stat.isDirectory() || stat.uid !== uid || stat.mode & 63)
|
|
150
|
+
return;
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const id = import_crypto.default.createHash("sha1").update([import_path.default.join(__dirname, ".."), process.execPath, cwd].join("\0")).digest("hex").slice(0, 16);
|
|
155
|
+
const socketFile = import_path.default.join(directory, `${id}.sock`);
|
|
156
|
+
return Buffer.byteLength(socketFile) < 100 ? socketFile : void 0;
|
|
157
|
+
}
|
|
158
|
+
function runInSession(request) {
|
|
159
|
+
const socketFile = sessionSocketPath(request.cwd);
|
|
160
|
+
if (!socketFile)
|
|
161
|
+
return Promise.resolve("local");
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
const socket = import_net.default.connect(socketFile);
|
|
164
|
+
let started = false;
|
|
165
|
+
let exited = false;
|
|
166
|
+
let buffer = "";
|
|
167
|
+
const lost = () => {
|
|
168
|
+
process.stderr.write("Flashwright session: the connection to the test run was lost.\n");
|
|
169
|
+
process.exit(1);
|
|
170
|
+
};
|
|
171
|
+
socket.on("error", () => {
|
|
172
|
+
if (started)
|
|
173
|
+
lost();
|
|
174
|
+
else
|
|
175
|
+
resolve("start");
|
|
176
|
+
});
|
|
177
|
+
socket.on("close", () => {
|
|
178
|
+
if (!started)
|
|
179
|
+
resolve("local");
|
|
180
|
+
else if (!exited)
|
|
181
|
+
lost();
|
|
182
|
+
});
|
|
183
|
+
socket.on("connect", () => socket.write(JSON.stringify(request) + "\n"));
|
|
184
|
+
socket.on("data", (chunk) => {
|
|
185
|
+
buffer += chunk.toString();
|
|
186
|
+
let newline;
|
|
187
|
+
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
188
|
+
const message = JSON.parse(buffer.slice(0, newline));
|
|
189
|
+
buffer = buffer.slice(newline + 1);
|
|
190
|
+
if (message.type === "cold") {
|
|
191
|
+
socket.destroy();
|
|
192
|
+
resolve(message.stale ? "start" : "local");
|
|
193
|
+
} else if (message.type === "started") {
|
|
194
|
+
started = true;
|
|
195
|
+
let interrupted = false;
|
|
196
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
197
|
+
process.on(signal, () => {
|
|
198
|
+
if (interrupted)
|
|
199
|
+
process.exit(130);
|
|
200
|
+
interrupted = true;
|
|
201
|
+
socket.write(JSON.stringify({ type: "signal", signal }) + "\n");
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} else if (message.type === "stdout" || message.type === "stderr") {
|
|
205
|
+
(message.type === "stdout" ? process.stdout : process.stderr).write(Buffer.from(message.data, "base64"));
|
|
206
|
+
} else if (message.type === "exit") {
|
|
207
|
+
exited = true;
|
|
208
|
+
socket.destroy();
|
|
209
|
+
const code = message.code ?? (message.signal === "SIGINT" ? 130 : 1);
|
|
210
|
+
process.stdout.write("", () => process.stderr.write("", () => process.exit(code)));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function startSession(request) {
|
|
217
|
+
const socketFile = sessionSocketPath(request.cwd);
|
|
218
|
+
if (!socketFile)
|
|
219
|
+
return;
|
|
220
|
+
const child = import_child_process.default.spawn(process.execPath, [__filename, "--flashwright-session", socketFile, request.build], {
|
|
221
|
+
detached: true,
|
|
222
|
+
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
223
|
+
env: { ...process.env, FLASHWRIGHT_SESSION_ROLE: "session" }
|
|
224
|
+
});
|
|
225
|
+
child.on("error", () => {
|
|
226
|
+
});
|
|
227
|
+
child.unref();
|
|
228
|
+
child.channel?.unref?.();
|
|
229
|
+
child.send({ method: "request", params: request });
|
|
230
|
+
joinSession(child, false);
|
|
231
|
+
}
|
|
232
|
+
var sessionChannel;
|
|
233
|
+
var offersBrowsers = false;
|
|
234
|
+
var lastRequestId = 0;
|
|
235
|
+
var standbyRequests = /* @__PURE__ */ new Map();
|
|
236
|
+
var workerParts = /* @__PURE__ */ new Map();
|
|
237
|
+
var pendingSends = /* @__PURE__ */ new Set();
|
|
238
|
+
var sessionWorkers = /* @__PURE__ */ new Set();
|
|
239
|
+
var sessionStandbys = /* @__PURE__ */ new Set();
|
|
240
|
+
function joinSession(channel, offers) {
|
|
241
|
+
sessionChannel = channel;
|
|
242
|
+
offersBrowsers = offers;
|
|
243
|
+
process.env.FLASHWRIGHT_SESSION_HANDOFF = "1";
|
|
244
|
+
}
|
|
245
|
+
function sessionStandbyEnv() {
|
|
246
|
+
return offersBrowsers && sessionChannel?.connected ? { FLASHWRIGHT_SESSION_OFFER: "1" } : {};
|
|
247
|
+
}
|
|
248
|
+
function requestSessionBrowser(child) {
|
|
249
|
+
if (!sessionChannel)
|
|
250
|
+
return;
|
|
251
|
+
sessionStandbys.add(child);
|
|
252
|
+
child.once("exit", () => sessionStandbys.delete(child));
|
|
253
|
+
if (!offersBrowsers || !sessionChannel.connected)
|
|
254
|
+
return;
|
|
255
|
+
const id = String(++lastRequestId);
|
|
256
|
+
standbyRequests.set(id, child);
|
|
257
|
+
child.once("exit", () => standbyRequests.delete(id));
|
|
258
|
+
sessionChannel.send({ method: kBrowserRequest, params: { id } });
|
|
259
|
+
}
|
|
260
|
+
function receiveWorkerMessage(child, message, handle) {
|
|
261
|
+
if (message?.method !== kBrowserMessage)
|
|
262
|
+
return false;
|
|
263
|
+
const browser = collectBrowserPart(workerParts, message, handle);
|
|
264
|
+
if (browser)
|
|
265
|
+
forwardBrowser(browser);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
function trackSessionWorker(child) {
|
|
269
|
+
if (!sessionChannel)
|
|
270
|
+
return;
|
|
271
|
+
sessionStandbys.delete(child);
|
|
272
|
+
sessionWorkers.add(child);
|
|
273
|
+
child.once("disconnect", () => sessionWorkers.delete(child));
|
|
274
|
+
}
|
|
275
|
+
function forwardBrowser(browser) {
|
|
276
|
+
if (!sessionChannel?.connected || browser.read.readableLength) {
|
|
277
|
+
killBrowser(browser);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
pendingSends.add(sendBrowser(sessionChannel, browser));
|
|
281
|
+
}
|
|
282
|
+
async function flushSessionBrowsers(exitCode) {
|
|
283
|
+
if (!sessionChannel)
|
|
284
|
+
return;
|
|
285
|
+
await Promise.all([...sessionStandbys].map(returnStandbyBrowser));
|
|
286
|
+
const deadline = Date.now() + 2e3;
|
|
287
|
+
while (sessionWorkers.size && Date.now() < deadline)
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
289
|
+
await Promise.all(pendingSends);
|
|
290
|
+
for (const parts of workerParts.values())
|
|
291
|
+
killBrowser(parts);
|
|
292
|
+
workerParts.clear();
|
|
293
|
+
if (outputBytes && exitCode !== void 0 && sessionChannel.connected)
|
|
294
|
+
await new Promise((resolve) => sessionChannel.send({ type: "exiting", code: exitCode, ...outputBytes }, void 0, resolve));
|
|
295
|
+
if (sessionChannel.connected)
|
|
296
|
+
sessionChannel.disconnect();
|
|
297
|
+
}
|
|
298
|
+
var outputBytes;
|
|
299
|
+
function countOutputBytes() {
|
|
300
|
+
outputBytes = { stdout: 0, stderr: 0 };
|
|
301
|
+
for (const name of ["stdout", "stderr"]) {
|
|
302
|
+
const stream = process[name];
|
|
303
|
+
const write = stream.write;
|
|
304
|
+
stream.write = function(chunk, encoding, callback) {
|
|
305
|
+
outputBytes[name] += typeof chunk === "string" ? Buffer.byteLength(chunk, typeof encoding === "string" ? encoding : "utf8") : chunk.length;
|
|
306
|
+
return write.apply(this, arguments);
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function returnStandbyBrowser(child) {
|
|
311
|
+
if (!child.connected)
|
|
312
|
+
return Promise.resolve();
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
const done = () => {
|
|
315
|
+
clearTimeout(timer);
|
|
316
|
+
child.off("message", onMessage);
|
|
317
|
+
resolve();
|
|
318
|
+
};
|
|
319
|
+
const timer = setTimeout(done, 2e3);
|
|
320
|
+
const onMessage = (message, handle) => {
|
|
321
|
+
if (message?.method === kBrowserReturned)
|
|
322
|
+
done();
|
|
323
|
+
else if (message?.method === kBrowserMessage)
|
|
324
|
+
receiveWorkerMessage(child, message, handle);
|
|
325
|
+
};
|
|
326
|
+
child.on("message", onMessage);
|
|
327
|
+
child.once("exit", done);
|
|
328
|
+
child.send({ method: kReturnBrowser });
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function onSessionMessage(message, handle) {
|
|
332
|
+
if (message?.method !== kBrowserMessage)
|
|
333
|
+
return;
|
|
334
|
+
const child = standbyRequests.get(message.params.id);
|
|
335
|
+
if (message.params.part !== "write")
|
|
336
|
+
standbyRequests.delete(message.params.id);
|
|
337
|
+
if (child?.connected) {
|
|
338
|
+
child.send(message, handle);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const browser = collectBrowserPart(workerParts, message, handle);
|
|
342
|
+
if (browser)
|
|
343
|
+
forwardBrowser(browser);
|
|
344
|
+
}
|
|
345
|
+
function collectBrowserPart(parts, message, handle) {
|
|
346
|
+
const { id, part, metadata } = message.params;
|
|
347
|
+
if (!part || !handle)
|
|
348
|
+
return;
|
|
349
|
+
const browser = parts.get(id) ?? { id };
|
|
350
|
+
browser[part] = handle;
|
|
351
|
+
if (metadata)
|
|
352
|
+
browser.metadata = metadata;
|
|
353
|
+
parts.set(id, browser);
|
|
354
|
+
if (!browser.write || !browser.read || !browser.metadata)
|
|
355
|
+
return;
|
|
356
|
+
parts.delete(id);
|
|
357
|
+
return browser;
|
|
358
|
+
}
|
|
359
|
+
function sendBrowser(channel, browser) {
|
|
360
|
+
const { id, metadata } = browser;
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
channel.send({ method: kBrowserMessage, params: { id, part: "write", metadata } }, browser.write, (error) => {
|
|
363
|
+
if (error) {
|
|
364
|
+
killBrowser(browser);
|
|
365
|
+
resolve();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
channel.send({ method: kBrowserMessage, params: { id, part: "read" } }, browser.read, () => {
|
|
369
|
+
channel.send({ method: kBrowserMessage, params: { id, sent: true } }, void 0, () => resolve());
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function killBrowser(browser) {
|
|
375
|
+
browser.write?.destroy();
|
|
376
|
+
browser.read?.destroy();
|
|
377
|
+
if (!browser.metadata)
|
|
378
|
+
return;
|
|
379
|
+
try {
|
|
380
|
+
process.kill(-browser.metadata.pid, "SIGKILL");
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
const directories = browser.metadata.tempDirectories;
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
for (const directory of directories)
|
|
386
|
+
import_fs.default.rm(directory, { recursive: true, force: true, maxRetries: 5 }, () => {
|
|
387
|
+
});
|
|
388
|
+
}, 500).unref();
|
|
389
|
+
}
|
|
390
|
+
function runSession(socketFile, build) {
|
|
391
|
+
const warmProcesses = [];
|
|
392
|
+
const browsers = [];
|
|
393
|
+
const waiting = [];
|
|
394
|
+
let startingRunKey;
|
|
395
|
+
let lastRequest;
|
|
396
|
+
let idleTimer;
|
|
397
|
+
let activeRuns = 0;
|
|
398
|
+
const shutdown = () => {
|
|
399
|
+
for (const warm of warmProcesses.splice(0))
|
|
400
|
+
warm.child.kill();
|
|
401
|
+
for (const browser of browsers.splice(0))
|
|
402
|
+
killBrowser(browser);
|
|
403
|
+
server.close();
|
|
404
|
+
try {
|
|
405
|
+
import_fs.default.unlinkSync(socketFile);
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
process.exit(0);
|
|
409
|
+
};
|
|
410
|
+
const resetIdleTimer = () => {
|
|
411
|
+
clearTimeout(idleTimer);
|
|
412
|
+
idleTimer = setTimeout(() => {
|
|
413
|
+
if (!activeRuns && !startingRunKey)
|
|
414
|
+
shutdown();
|
|
415
|
+
}, kIdleTimeout);
|
|
416
|
+
};
|
|
417
|
+
const runsThatMayHandOver = (key) => (startingRunKey === key ? 1 : 0) + warmProcesses.filter((warm) => warm.running && warm.connected && warm.key === key).length;
|
|
418
|
+
const answer = (warm, id) => {
|
|
419
|
+
let browser;
|
|
420
|
+
for (const candidate of browsers) {
|
|
421
|
+
if (candidate.key === warm.key && (!browser || (candidate.metadata.lastUsed ?? 0) > (browser.metadata.lastUsed ?? 0)))
|
|
422
|
+
browser = candidate;
|
|
423
|
+
}
|
|
424
|
+
if (browser)
|
|
425
|
+
remove(browser);
|
|
426
|
+
if (!warm.child.connected) {
|
|
427
|
+
if (browser)
|
|
428
|
+
browsers.push(browser);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!browser) {
|
|
432
|
+
warm.child.send({ method: kBrowserMessage, params: { id, none: true } });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
browser.read.removeAllListeners("data");
|
|
436
|
+
browser.read.removeAllListeners("close");
|
|
437
|
+
browser.read.pause();
|
|
438
|
+
void sendBrowser(warm.child, { ...browser, id });
|
|
439
|
+
};
|
|
440
|
+
const onBrowserRequest = (warm, id) => {
|
|
441
|
+
if (browsers.some((browser) => browser.key === warm.key) || warm.running || !runsThatMayHandOver(warm.key))
|
|
442
|
+
answer(warm, id);
|
|
443
|
+
else
|
|
444
|
+
waiting.push({ warm, id });
|
|
445
|
+
};
|
|
446
|
+
const answerWaiting = (predicate) => {
|
|
447
|
+
for (const entry of waiting.filter(predicate)) {
|
|
448
|
+
waiting.splice(waiting.indexOf(entry), 1);
|
|
449
|
+
answer(entry.warm, entry.id);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const onBrowserReturned = (key, browser) => {
|
|
453
|
+
if (browser.read.readableLength) {
|
|
454
|
+
killBrowser(browser);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const pooled = { ...browser, key };
|
|
458
|
+
browser.read.on("data", () => {
|
|
459
|
+
remove(pooled);
|
|
460
|
+
killBrowser(pooled);
|
|
461
|
+
});
|
|
462
|
+
browser.read.on("close", () => {
|
|
463
|
+
remove(pooled);
|
|
464
|
+
killBrowser(pooled);
|
|
465
|
+
});
|
|
466
|
+
browsers.push(pooled);
|
|
467
|
+
const entry = waiting.find((entry2) => entry2.warm.key === key);
|
|
468
|
+
if (entry) {
|
|
469
|
+
waiting.splice(waiting.indexOf(entry), 1);
|
|
470
|
+
answer(entry.warm, entry.id);
|
|
471
|
+
}
|
|
472
|
+
const sameKind = browsers.filter((browser2) => browser2.key === key).sort((a, b) => (a.metadata.lastUsed ?? 0) - (b.metadata.lastUsed ?? 0));
|
|
473
|
+
for (const extra of sameKind.slice(0, Math.max(0, sameKind.length - kMaxBrowsers))) {
|
|
474
|
+
remove(extra);
|
|
475
|
+
killBrowser(extra);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const remove = (browser) => {
|
|
479
|
+
const index = browsers.indexOf(browser);
|
|
480
|
+
if (index !== -1)
|
|
481
|
+
browsers.splice(index, 1);
|
|
482
|
+
};
|
|
483
|
+
const spawnWarm = (request) => {
|
|
484
|
+
const key = warmKey(request);
|
|
485
|
+
if (warmProcesses.some((warm2) => warm2.key === key && !warm2.running))
|
|
486
|
+
return;
|
|
487
|
+
const standbys = browsers.filter((browser) => browser.key === key).length;
|
|
488
|
+
const child = import_child_process.default.spawn(request.execPath, [...request.execArgv, request.cliPath, ...request.argv.slice(2)], {
|
|
489
|
+
cwd: request.cwd,
|
|
490
|
+
env: { ...warmEnv(request), FLASHWRIGHT_SESSION_ROLE: "warm", FLASHWRIGHT_SESSION_STANDBYS: String(standbys) },
|
|
491
|
+
stdio: ["ignore", "pipe", "pipe", "ipc"]
|
|
492
|
+
});
|
|
493
|
+
const warm = { child, key, ready: Promise.resolve(false), running: false, connected: true, parts: /* @__PURE__ */ new Map() };
|
|
494
|
+
warm.ready = new Promise((resolve) => {
|
|
495
|
+
child.on("message", (message, handle2) => {
|
|
496
|
+
if (message?.type === "warm") {
|
|
497
|
+
resolve(true);
|
|
498
|
+
} else if (message?.type === "exiting") {
|
|
499
|
+
warm.exiting?.(message);
|
|
500
|
+
} else if (message?.method === kBrowserRequest) {
|
|
501
|
+
onBrowserRequest(warm, message.params.id);
|
|
502
|
+
} else if (message?.method === kBrowserMessage) {
|
|
503
|
+
const browser = collectBrowserPart(warm.parts, message, handle2);
|
|
504
|
+
if (browser)
|
|
505
|
+
onBrowserReturned(key, browser);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
child.once("exit", () => resolve(false));
|
|
509
|
+
});
|
|
510
|
+
warmProcesses.push(warm);
|
|
511
|
+
child.once("disconnect", () => {
|
|
512
|
+
warm.connected = false;
|
|
513
|
+
for (const parts2 of warm.parts.values())
|
|
514
|
+
killBrowser(parts2);
|
|
515
|
+
warm.parts.clear();
|
|
516
|
+
answerWaiting((entry) => entry.warm === warm || !runsThatMayHandOver(entry.warm.key));
|
|
517
|
+
});
|
|
518
|
+
child.once("exit", () => {
|
|
519
|
+
const index = warmProcesses.indexOf(warm);
|
|
520
|
+
if (index !== -1)
|
|
521
|
+
warmProcesses.splice(index, 1);
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
const handle = async (socket, request) => {
|
|
525
|
+
resetIdleTimer();
|
|
526
|
+
if (request.build !== build) {
|
|
527
|
+
socket.end(JSON.stringify({ type: "cold", stale: true }) + "\n");
|
|
528
|
+
shutdown();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
lastRequest = request;
|
|
532
|
+
const key = warmKey(request);
|
|
533
|
+
const index = warmProcesses.findIndex((warm2) => warm2.key === key && !warm2.running);
|
|
534
|
+
const warm = index === -1 ? void 0 : warmProcesses[index];
|
|
535
|
+
if (!warm || !await warm.ready) {
|
|
536
|
+
warm?.child.kill();
|
|
537
|
+
socket.end(JSON.stringify({ type: "cold" }) + "\n");
|
|
538
|
+
if (!startingRunKey)
|
|
539
|
+
spawnWarm(request);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
activeRuns++;
|
|
543
|
+
warm.running = true;
|
|
544
|
+
answerWaiting((entry) => entry.warm === warm);
|
|
545
|
+
const send = (message) => {
|
|
546
|
+
if (!socket.destroyed)
|
|
547
|
+
socket.write(JSON.stringify(message) + "\n");
|
|
548
|
+
};
|
|
549
|
+
send({ type: "started" });
|
|
550
|
+
const relayed = { stdout: 0, stderr: 0 };
|
|
551
|
+
let exiting;
|
|
552
|
+
let finished = false;
|
|
553
|
+
const finish = (code, signal) => {
|
|
554
|
+
if (finished)
|
|
555
|
+
return;
|
|
556
|
+
finished = true;
|
|
557
|
+
send({ type: "exit", code, signal });
|
|
558
|
+
socket.end();
|
|
559
|
+
};
|
|
560
|
+
const finishIfRelayed = () => {
|
|
561
|
+
if (exiting && relayed.stdout >= exiting.stdout && relayed.stderr >= exiting.stderr)
|
|
562
|
+
finish(exiting.code, null);
|
|
563
|
+
};
|
|
564
|
+
warm.exiting = (message) => {
|
|
565
|
+
exiting = message;
|
|
566
|
+
finishIfRelayed();
|
|
567
|
+
};
|
|
568
|
+
for (const name of ["stdout", "stderr"]) {
|
|
569
|
+
warm.child[name].on("data", (data) => {
|
|
570
|
+
send({ type: name, data: data.toString("base64") });
|
|
571
|
+
relayed[name] += data.length;
|
|
572
|
+
finishIfRelayed();
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
let buffer = "";
|
|
576
|
+
socket.on("data", (chunk) => {
|
|
577
|
+
buffer += chunk.toString();
|
|
578
|
+
let newline;
|
|
579
|
+
while ((newline = buffer.indexOf("\n")) !== -1) {
|
|
580
|
+
const message = JSON.parse(buffer.slice(0, newline));
|
|
581
|
+
buffer = buffer.slice(newline + 1);
|
|
582
|
+
if (message.type === "signal")
|
|
583
|
+
warm.child.kill(message.signal);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
socket.on("close", () => {
|
|
587
|
+
if (warm.child.exitCode === null && warm.child.signalCode === null)
|
|
588
|
+
warm.child.kill("SIGINT");
|
|
589
|
+
});
|
|
590
|
+
warm.child.once("exit", (code, signal) => {
|
|
591
|
+
activeRuns--;
|
|
592
|
+
resetIdleTimer();
|
|
593
|
+
if (warm.child.stdout.readableEnded && warm.child.stderr.readableEnded)
|
|
594
|
+
finish(code, signal);
|
|
595
|
+
else
|
|
596
|
+
warm.child.once("close", () => finish(code, signal));
|
|
597
|
+
spawnWarm(lastRequest ?? request);
|
|
598
|
+
});
|
|
599
|
+
warm.child.send(request);
|
|
600
|
+
};
|
|
601
|
+
const listen = () => server.listen(socketFile, () => import_fs.default.chmodSync(socketFile, 384));
|
|
602
|
+
const server = import_net.default.createServer((socket) => {
|
|
603
|
+
let buffer = "";
|
|
604
|
+
const onData = (chunk) => {
|
|
605
|
+
buffer += chunk.toString();
|
|
606
|
+
const newline = buffer.indexOf("\n");
|
|
607
|
+
if (newline === -1)
|
|
608
|
+
return;
|
|
609
|
+
socket.off("data", onData);
|
|
610
|
+
const request = JSON.parse(buffer.slice(0, newline));
|
|
611
|
+
handle(socket, request).catch(() => socket.destroy());
|
|
612
|
+
};
|
|
613
|
+
socket.on("data", onData);
|
|
614
|
+
socket.on("error", () => {
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
server.on("error", (error) => {
|
|
618
|
+
if (error.code !== "EADDRINUSE")
|
|
619
|
+
process.exit(0);
|
|
620
|
+
const probe = import_net.default.connect(socketFile);
|
|
621
|
+
probe.on("connect", () => process.exit(0));
|
|
622
|
+
probe.on("error", () => {
|
|
623
|
+
try {
|
|
624
|
+
import_fs.default.unlinkSync(socketFile);
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
627
|
+
listen();
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
listen();
|
|
631
|
+
resetIdleTimer();
|
|
632
|
+
process.on("SIGTERM", shutdown);
|
|
633
|
+
const parts = /* @__PURE__ */ new Map();
|
|
634
|
+
process.on("message", (message, handle2) => {
|
|
635
|
+
if (message?.method === "request") {
|
|
636
|
+
lastRequest = message.params;
|
|
637
|
+
startingRunKey = warmKey(message.params);
|
|
638
|
+
} else if (message?.method === kBrowserMessage && startingRunKey) {
|
|
639
|
+
const browser = collectBrowserPart(parts, message, handle2);
|
|
640
|
+
if (browser)
|
|
641
|
+
onBrowserReturned(startingRunKey, browser);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
process.on("disconnect", () => {
|
|
645
|
+
for (const browser of parts.values())
|
|
646
|
+
killBrowser(browser);
|
|
647
|
+
parts.clear();
|
|
648
|
+
startingRunKey = void 0;
|
|
649
|
+
resetIdleTimer();
|
|
650
|
+
if (lastRequest)
|
|
651
|
+
spawnWarm(lastRequest);
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
function warmEnv(request) {
|
|
655
|
+
const env = { ...request.env };
|
|
656
|
+
const injected = [];
|
|
657
|
+
if (request.profile.stdout !== "false") {
|
|
658
|
+
for (const [key, value] of [["PLAYWRIGHT_FORCE_TTY", request.profile.stdout], ["FORCE_COLOR", "1"]]) {
|
|
659
|
+
if (env[key] === void 0) {
|
|
660
|
+
env[key] = value;
|
|
661
|
+
injected.push(key);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
env.FLASHWRIGHT_SESSION_INJECTED = injected.join(",");
|
|
666
|
+
return env;
|
|
667
|
+
}
|
|
668
|
+
async function runWarmProcess(hooks) {
|
|
669
|
+
joinSession(process, true);
|
|
670
|
+
process.on("message", onSessionMessage);
|
|
671
|
+
hooks.installCodeCache("runner");
|
|
672
|
+
const program = hooks.loadProgram();
|
|
673
|
+
require(import_path.default.join(__dirname, "index.js"));
|
|
674
|
+
const packageRoot = import_path.default.join(__dirname, "..", "..");
|
|
675
|
+
require(import_path.default.join(packageRoot, "test.js"));
|
|
676
|
+
await import(import_url.default.pathToFileURL(import_path.default.join(packageRoot, "test.mjs")).href).catch(() => {
|
|
677
|
+
});
|
|
678
|
+
await import(import_url.default.pathToFileURL(import_path.default.join(import_path.default.dirname(process.argv[1]), "index.mjs")).href).catch(() => {
|
|
679
|
+
});
|
|
680
|
+
for (const key of (process.env.FLASHWRIGHT_SESSION_INJECTED || "").split(",").filter(Boolean))
|
|
681
|
+
delete process.env[key];
|
|
682
|
+
delete process.env.FLASHWRIGHT_SESSION_INJECTED;
|
|
683
|
+
delete process.env.FLASHWRIGHT_SESSION_ROLE;
|
|
684
|
+
hooks.prespawnWorkers(process.argv);
|
|
685
|
+
const request = await new Promise((resolve) => {
|
|
686
|
+
const onMessage = (message) => {
|
|
687
|
+
if (message?.type !== "run")
|
|
688
|
+
return;
|
|
689
|
+
process.off("message", onMessage);
|
|
690
|
+
resolve(message);
|
|
691
|
+
};
|
|
692
|
+
process.on("message", onMessage);
|
|
693
|
+
process.send({ type: "warm" });
|
|
694
|
+
});
|
|
695
|
+
process.channel?.unref();
|
|
696
|
+
process.chdir(request.cwd);
|
|
697
|
+
for (const key of Object.keys(process.env)) {
|
|
698
|
+
if (!(key in request.env) && key !== "FLASHWRIGHT_SESSION_HANDOFF")
|
|
699
|
+
delete process.env[key];
|
|
700
|
+
}
|
|
701
|
+
Object.assign(process.env, request.env);
|
|
702
|
+
process.argv = request.argv;
|
|
703
|
+
if (request.profile.stdout !== "false") {
|
|
704
|
+
const [columns, rows] = request.profile.stdout.split("x").map(Number);
|
|
705
|
+
Object.assign(process.stdout, { isTTY: true, columns, rows });
|
|
706
|
+
}
|
|
707
|
+
if (request.profile.stdinTTY)
|
|
708
|
+
Object.assign(process.stdin, { isTTY: true });
|
|
709
|
+
countOutputBytes();
|
|
710
|
+
hooks.retargetWorkers(request.argv);
|
|
711
|
+
program.parse(request.argv);
|
|
712
|
+
}
|
|
713
|
+
if (process.argv[2] === "--flashwright-session")
|
|
714
|
+
runSession(process.argv[3], process.argv[4]);
|
|
715
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
716
|
+
0 && (module.exports = {
|
|
717
|
+
flushSessionBrowsers,
|
|
718
|
+
receiveWorkerMessage,
|
|
719
|
+
requestSessionBrowser,
|
|
720
|
+
runCli,
|
|
721
|
+
sessionStandbyEnv,
|
|
722
|
+
trackSessionWorker
|
|
723
|
+
});
|
|
@@ -36,6 +36,7 @@ __export(workerStandby_exports, {
|
|
|
36
36
|
prespawnWorkerForTestCommand: () => prespawnWorkerForTestCommand,
|
|
37
37
|
recordWorkerCount: () => recordWorkerCount,
|
|
38
38
|
recordWorkerRestart: () => recordWorkerRestart,
|
|
39
|
+
retargetStandbys: () => retargetStandbys,
|
|
39
40
|
takeStandby: () => takeStandby,
|
|
40
41
|
workerEntryScript: () => workerEntryScript,
|
|
41
42
|
workerEnv: () => workerEnv
|
|
@@ -46,6 +47,7 @@ var import_crypto = __toESM(require("crypto"));
|
|
|
46
47
|
var import_fs = __toESM(require("fs"));
|
|
47
48
|
var import_os = __toESM(require("os"));
|
|
48
49
|
var import_path = __toESM(require("path"));
|
|
50
|
+
var import_session = require("./session");
|
|
49
51
|
var standbys = [];
|
|
50
52
|
var enabled = false;
|
|
51
53
|
var hintKey;
|
|
@@ -78,7 +80,8 @@ function prespawnWorkerForTestCommand(argv) {
|
|
|
78
80
|
cpus = import_os.default.availableParallelism?.() ?? import_os.default.cpus().length;
|
|
79
81
|
const hint = readWorkerCountHints()[hintKey];
|
|
80
82
|
hadHint = !!hint;
|
|
81
|
-
const
|
|
83
|
+
const sessionBrowsers = Number(process.env.FLASHWRIGHT_SESSION_STANDBYS) || 0;
|
|
84
|
+
const count = Math.max(1, Math.min(Math.max(hint?.workers || 1, sessionBrowsers), cpus));
|
|
82
85
|
keepSpare = !!hint?.restarts && count < cpus;
|
|
83
86
|
for (let i = 0; i < count + (keepSpare ? 1 : 0); i++)
|
|
84
87
|
spawnStandby();
|
|
@@ -89,7 +92,7 @@ function prespawnWorkerForTestCommand(argv) {
|
|
|
89
92
|
}
|
|
90
93
|
function spawnStandby(waitsForNextPhase = false) {
|
|
91
94
|
const entryScript = workerEntryScript();
|
|
92
|
-
const env = { ...workerEnv({}), PLAYWRIGHT_TEST: "1", FLASHWRIGHT_SPECULATIVE_BROWSER: waitsForNextPhase ? "next-phase" : "1" };
|
|
95
|
+
const env = { ...workerEnv({}), PLAYWRIGHT_TEST: "1", FLASHWRIGHT_SPECULATIVE_BROWSER: waitsForNextPhase ? "next-phase" : "1", ...(0, import_session.sessionStandbyEnv)() };
|
|
93
96
|
const child = import_child_process.default.fork(entryScript, {
|
|
94
97
|
detached: false,
|
|
95
98
|
env,
|
|
@@ -97,6 +100,7 @@ function spawnStandby(waitsForNextPhase = false) {
|
|
|
97
100
|
});
|
|
98
101
|
child.on("error", () => {
|
|
99
102
|
});
|
|
103
|
+
(0, import_session.requestSessionBrowser)(child);
|
|
100
104
|
const ready = new Promise((resolve) => {
|
|
101
105
|
const onMessage = (message) => {
|
|
102
106
|
if (message?.method === "ready") {
|
|
@@ -128,6 +132,10 @@ function ensureStandbysForConfig(configuredWorkers) {
|
|
|
128
132
|
if (enabled && !hadHint)
|
|
129
133
|
ensureStandbys(Math.min(configuredWorkers, cpus));
|
|
130
134
|
}
|
|
135
|
+
function retargetStandbys(argv) {
|
|
136
|
+
if (enabled)
|
|
137
|
+
hintKey = workerCountHintKey(argv);
|
|
138
|
+
}
|
|
131
139
|
function recordWorkerCount(count) {
|
|
132
140
|
workerCount = Math.max(workerCount, count);
|
|
133
141
|
}
|
|
@@ -191,6 +199,7 @@ function writeWorkerCountHint() {
|
|
|
191
199
|
prespawnWorkerForTestCommand,
|
|
192
200
|
recordWorkerCount,
|
|
193
201
|
recordWorkerRestart,
|
|
202
|
+
retargetStandbys,
|
|
194
203
|
takeStandby,
|
|
195
204
|
workerEntryScript,
|
|
196
205
|
workerEnv
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# packages/playwright/lib/runner/workerStandby.js
|
|
2
|
-
# total: 7.
|
|
2
|
+
# total: 7.6 KB
|
|
3
3
|
|
|
4
4
|
## Inlined (1)
|
|
5
|
-
5.
|
|
5
|
+
5.9 KB packages/playwright/src/runner/workerStandby.ts
|
|
6
6
|
|
|
7
|
-
## External (
|
|
7
|
+
## External (1)
|
|
8
|
+
./session
|
|
@@ -28,9 +28,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var speculativeBrowser_exports = {};
|
|
30
30
|
__export(speculativeBrowser_exports, {
|
|
31
|
+
handBrowsersToSession: () => handBrowsersToSession,
|
|
31
32
|
launchSpeculativeBrowser: () => launchSpeculativeBrowser,
|
|
32
33
|
prepareSpeculativeContext: () => prepareSpeculativeContext,
|
|
33
34
|
rememberRecycledContext: () => rememberRecycledContext,
|
|
35
|
+
returnSpeculativeBrowser: () => returnSpeculativeBrowser,
|
|
34
36
|
takeSpeculativeBrowser: () => takeSpeculativeBrowser
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(speculativeBrowser_exports);
|
|
@@ -41,11 +43,100 @@ var import_util = require("util");
|
|
|
41
43
|
const speculativeOptions = { handleSIGINT: false, headless: true, ...process.env.FLASHWRIGHT_ISOLATION === "strict" ? {} : { recycleContexts: true } };
|
|
42
44
|
const speculativeKey = /* @__PURE__ */ Symbol.for("flashwright.speculativeBrowser");
|
|
43
45
|
function launchSpeculativeBrowser() {
|
|
44
|
-
const
|
|
45
|
-
const launch = playwright.chromium.launch(speculativeOptions).catch(() => void 0);
|
|
46
|
+
const offered = process.env.FLASHWRIGHT_SESSION_OFFER ? receiveSessionBrowser() : Promise.resolve();
|
|
47
|
+
const launch = offered.then(() => require("playwright-core").chromium.launch(speculativeOptions)).catch(() => void 0);
|
|
46
48
|
globalThis[speculativeKey] = launch;
|
|
47
49
|
return launch;
|
|
48
50
|
}
|
|
51
|
+
const kBrowserMessage = "__flashwright_browser__";
|
|
52
|
+
const offerKey = /* @__PURE__ */ Symbol.for("flashwright.browserOffer");
|
|
53
|
+
let offeredLastUsed = 0;
|
|
54
|
+
const kOfferTimeoutAfterInit = 500;
|
|
55
|
+
function receiveSessionBrowser() {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
let answered = false;
|
|
58
|
+
let timer;
|
|
59
|
+
const answer = () => {
|
|
60
|
+
answered = true;
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
resolve();
|
|
63
|
+
};
|
|
64
|
+
const parts = {};
|
|
65
|
+
const onMessage = (message, handle) => {
|
|
66
|
+
if (message?.method === "__init__" && !answered)
|
|
67
|
+
timer = setTimeout(answer, kOfferTimeoutAfterInit);
|
|
68
|
+
if (message?.method !== kBrowserMessage || message.params.sent)
|
|
69
|
+
return;
|
|
70
|
+
if (message.params.none) {
|
|
71
|
+
process.off("message", onMessage);
|
|
72
|
+
answer();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
parts[message.params.part] = handle;
|
|
76
|
+
parts.metadata ??= message.params.metadata;
|
|
77
|
+
if (!parts.write || !parts.read || !parts.metadata)
|
|
78
|
+
return;
|
|
79
|
+
process.off("message", onMessage);
|
|
80
|
+
if (answered) {
|
|
81
|
+
void sendToRunner(parts);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
globalThis[offerKey] = parts;
|
|
85
|
+
offeredLastUsed = parts.metadata.lastUsed ?? 0;
|
|
86
|
+
process.once("exit", () => {
|
|
87
|
+
if (globalThis[offerKey] === parts)
|
|
88
|
+
killHandedOverBrowser(parts.metadata);
|
|
89
|
+
});
|
|
90
|
+
answer();
|
|
91
|
+
};
|
|
92
|
+
process.on("message", onMessage);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function handBrowsersToSession(browser, ranTests) {
|
|
96
|
+
if (!process.env.FLASHWRIGHT_SESSION_HANDOFF || !process.send)
|
|
97
|
+
return false;
|
|
98
|
+
const offer = globalThis[offerKey];
|
|
99
|
+
if (offer) {
|
|
100
|
+
delete globalThis[offerKey];
|
|
101
|
+
await sendToRunner(offer);
|
|
102
|
+
}
|
|
103
|
+
if (!browser)
|
|
104
|
+
return false;
|
|
105
|
+
const serverBrowser = browser._connection.toImpl?.(browser);
|
|
106
|
+
const handoff = await serverBrowser?.handoffForSession?.().catch(() => void 0);
|
|
107
|
+
if (!handoff)
|
|
108
|
+
return false;
|
|
109
|
+
handoff.metadata.lastUsed = ranTests ? Date.now() : offeredLastUsed;
|
|
110
|
+
await sendToRunner(handoff);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
async function sendToRunner(browser) {
|
|
114
|
+
const id = `${process.pid}-${browser.metadata.pid}`;
|
|
115
|
+
const send = (params, handle) => new Promise((resolve) => {
|
|
116
|
+
if (!process.connected)
|
|
117
|
+
return resolve(false);
|
|
118
|
+
process.send({ method: kBrowserMessage, params }, handle, void 0, (error) => resolve(!error));
|
|
119
|
+
});
|
|
120
|
+
if (await send({ id, part: "write", metadata: browser.metadata }, browser.write) && await send({ id, part: "read" }, browser.read)) {
|
|
121
|
+
await send({ id, sent: true }, void 0);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
browser.write.destroy();
|
|
125
|
+
browser.read.destroy();
|
|
126
|
+
killHandedOverBrowser(browser.metadata);
|
|
127
|
+
}
|
|
128
|
+
function killHandedOverBrowser(metadata) {
|
|
129
|
+
try {
|
|
130
|
+
process.kill(-metadata.pid, "SIGKILL");
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
for (const directory of metadata.tempDirectories) {
|
|
134
|
+
try {
|
|
135
|
+
import_fs.default.rmSync(directory, { recursive: true, force: true, maxRetries: 5 });
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
49
140
|
function prepareSpeculativeContext() {
|
|
50
141
|
const launch = globalThis[speculativeKey];
|
|
51
142
|
const hint = speculativeOptions.recycleContexts ? readContextHint() : void 0;
|
|
@@ -100,6 +191,13 @@ function isPlainJSON(value) {
|
|
|
100
191
|
return value.every(isPlainJSON);
|
|
101
192
|
return typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype && Object.values(value).every(isPlainJSON);
|
|
102
193
|
}
|
|
194
|
+
async function returnSpeculativeBrowser() {
|
|
195
|
+
const launch = globalThis[speculativeKey];
|
|
196
|
+
if (!launch || !process.env.FLASHWRIGHT_SESSION_HANDOFF)
|
|
197
|
+
return;
|
|
198
|
+
delete globalThis[speculativeKey];
|
|
199
|
+
await handBrowsersToSession(await launch, false);
|
|
200
|
+
}
|
|
103
201
|
async function takeSpeculativeBrowser(browserName, options) {
|
|
104
202
|
const launch = globalThis[speculativeKey];
|
|
105
203
|
if (!launch)
|
|
@@ -121,8 +219,10 @@ async function takeSpeculativeBrowser(browserName, options) {
|
|
|
121
219
|
}
|
|
122
220
|
// Annotate the CommonJS export names for ESM import in node:
|
|
123
221
|
0 && (module.exports = {
|
|
222
|
+
handBrowsersToSession,
|
|
124
223
|
launchSpeculativeBrowser,
|
|
125
224
|
prepareSpeculativeContext,
|
|
126
225
|
rememberRecycledContext,
|
|
226
|
+
returnSpeculativeBrowser,
|
|
127
227
|
takeSpeculativeBrowser
|
|
128
228
|
});
|
|
@@ -1865,11 +1865,100 @@ var import_path4 = __toESM(require("path"));
|
|
|
1865
1865
|
var speculativeOptions = { handleSIGINT: false, headless: true, ...process.env.FLASHWRIGHT_ISOLATION === "strict" ? {} : { recycleContexts: true } };
|
|
1866
1866
|
var speculativeKey = /* @__PURE__ */ Symbol.for("flashwright.speculativeBrowser");
|
|
1867
1867
|
function launchSpeculativeBrowser() {
|
|
1868
|
-
const
|
|
1869
|
-
const launch = playwright.chromium.launch(speculativeOptions).catch(() => void 0);
|
|
1868
|
+
const offered = process.env.FLASHWRIGHT_SESSION_OFFER ? receiveSessionBrowser() : Promise.resolve();
|
|
1869
|
+
const launch = offered.then(() => require("playwright-core").chromium.launch(speculativeOptions)).catch(() => void 0);
|
|
1870
1870
|
globalThis[speculativeKey] = launch;
|
|
1871
1871
|
return launch;
|
|
1872
1872
|
}
|
|
1873
|
+
var kBrowserMessage = "__flashwright_browser__";
|
|
1874
|
+
var offerKey = /* @__PURE__ */ Symbol.for("flashwright.browserOffer");
|
|
1875
|
+
var offeredLastUsed = 0;
|
|
1876
|
+
var kOfferTimeoutAfterInit = 500;
|
|
1877
|
+
function receiveSessionBrowser() {
|
|
1878
|
+
return new Promise((resolve) => {
|
|
1879
|
+
let answered = false;
|
|
1880
|
+
let timer;
|
|
1881
|
+
const answer = () => {
|
|
1882
|
+
answered = true;
|
|
1883
|
+
clearTimeout(timer);
|
|
1884
|
+
resolve();
|
|
1885
|
+
};
|
|
1886
|
+
const parts = {};
|
|
1887
|
+
const onMessage = (message, handle) => {
|
|
1888
|
+
if (message?.method === "__init__" && !answered)
|
|
1889
|
+
timer = setTimeout(answer, kOfferTimeoutAfterInit);
|
|
1890
|
+
if (message?.method !== kBrowserMessage || message.params.sent)
|
|
1891
|
+
return;
|
|
1892
|
+
if (message.params.none) {
|
|
1893
|
+
process.off("message", onMessage);
|
|
1894
|
+
answer();
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
parts[message.params.part] = handle;
|
|
1898
|
+
parts.metadata ??= message.params.metadata;
|
|
1899
|
+
if (!parts.write || !parts.read || !parts.metadata)
|
|
1900
|
+
return;
|
|
1901
|
+
process.off("message", onMessage);
|
|
1902
|
+
if (answered) {
|
|
1903
|
+
void sendToRunner(parts);
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
globalThis[offerKey] = parts;
|
|
1907
|
+
offeredLastUsed = parts.metadata.lastUsed ?? 0;
|
|
1908
|
+
process.once("exit", () => {
|
|
1909
|
+
if (globalThis[offerKey] === parts)
|
|
1910
|
+
killHandedOverBrowser(parts.metadata);
|
|
1911
|
+
});
|
|
1912
|
+
answer();
|
|
1913
|
+
};
|
|
1914
|
+
process.on("message", onMessage);
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
async function handBrowsersToSession(browser, ranTests) {
|
|
1918
|
+
if (!process.env.FLASHWRIGHT_SESSION_HANDOFF || !process.send)
|
|
1919
|
+
return false;
|
|
1920
|
+
const offer = globalThis[offerKey];
|
|
1921
|
+
if (offer) {
|
|
1922
|
+
delete globalThis[offerKey];
|
|
1923
|
+
await sendToRunner(offer);
|
|
1924
|
+
}
|
|
1925
|
+
if (!browser)
|
|
1926
|
+
return false;
|
|
1927
|
+
const serverBrowser = browser._connection.toImpl?.(browser);
|
|
1928
|
+
const handoff = await serverBrowser?.handoffForSession?.().catch(() => void 0);
|
|
1929
|
+
if (!handoff)
|
|
1930
|
+
return false;
|
|
1931
|
+
handoff.metadata.lastUsed = ranTests ? Date.now() : offeredLastUsed;
|
|
1932
|
+
await sendToRunner(handoff);
|
|
1933
|
+
return true;
|
|
1934
|
+
}
|
|
1935
|
+
async function sendToRunner(browser) {
|
|
1936
|
+
const id = `${process.pid}-${browser.metadata.pid}`;
|
|
1937
|
+
const send = (params, handle) => new Promise((resolve) => {
|
|
1938
|
+
if (!process.connected)
|
|
1939
|
+
return resolve(false);
|
|
1940
|
+
process.send({ method: kBrowserMessage, params }, handle, void 0, (error) => resolve(!error));
|
|
1941
|
+
});
|
|
1942
|
+
if (await send({ id, part: "write", metadata: browser.metadata }, browser.write) && await send({ id, part: "read" }, browser.read)) {
|
|
1943
|
+
await send({ id, sent: true }, void 0);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
browser.write.destroy();
|
|
1947
|
+
browser.read.destroy();
|
|
1948
|
+
killHandedOverBrowser(browser.metadata);
|
|
1949
|
+
}
|
|
1950
|
+
function killHandedOverBrowser(metadata) {
|
|
1951
|
+
try {
|
|
1952
|
+
process.kill(-metadata.pid, "SIGKILL");
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
for (const directory of metadata.tempDirectories) {
|
|
1956
|
+
try {
|
|
1957
|
+
import_fs4.default.rmSync(directory, { recursive: true, force: true, maxRetries: 5 });
|
|
1958
|
+
} catch {
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1873
1962
|
function prepareSpeculativeContext() {
|
|
1874
1963
|
const launch = globalThis[speculativeKey];
|
|
1875
1964
|
const hint = speculativeOptions.recycleContexts ? readContextHint() : void 0;
|
|
@@ -1894,6 +1983,13 @@ function readContextHint() {
|
|
|
1894
1983
|
const key = process.env.FLASHWRIGHT_CONTEXT_HINT_KEY;
|
|
1895
1984
|
return key ? readContextHints()[key] : void 0;
|
|
1896
1985
|
}
|
|
1986
|
+
async function returnSpeculativeBrowser() {
|
|
1987
|
+
const launch = globalThis[speculativeKey];
|
|
1988
|
+
if (!launch || !process.env.FLASHWRIGHT_SESSION_HANDOFF)
|
|
1989
|
+
return;
|
|
1990
|
+
delete globalThis[speculativeKey];
|
|
1991
|
+
await handBrowsersToSession(await launch, false);
|
|
1992
|
+
}
|
|
1897
1993
|
|
|
1898
1994
|
// packages/playwright/src/worker/workerProcessEntry.ts
|
|
1899
1995
|
var kSpareIdleTime = 100;
|
|
@@ -1901,6 +1997,10 @@ if (process.env.FLASHWRIGHT_SPECULATIVE_BROWSER) {
|
|
|
1901
1997
|
let initialized = false;
|
|
1902
1998
|
process.on("message", (message) => initialized ||= message?.method === "__init__");
|
|
1903
1999
|
const idleTime = process.env.FLASHWRIGHT_SPECULATIVE_BROWSER === "next-phase" ? 0 : kSpareIdleTime;
|
|
2000
|
+
process.on("message", (message) => {
|
|
2001
|
+
if (message?.method === "__flashwright_return_browser__" && !initialized)
|
|
2002
|
+
void returnSpeculativeBrowser().finally(() => process.send?.({ method: "__flashwright_browser_returned__" }));
|
|
2003
|
+
});
|
|
1904
2004
|
void launchSpeculativeBrowser().then(() => setTimeout(() => {
|
|
1905
2005
|
if (initialized)
|
|
1906
2006
|
return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# packages/playwright/lib/worker/workerProcessEntry.js
|
|
2
|
-
# total:
|
|
2
|
+
# total: 79.7 KB
|
|
3
3
|
|
|
4
4
|
## Inlined (9)
|
|
5
|
-
|
|
5
|
+
4.6 KB packages/playwright/src/speculativeBrowser.ts
|
|
6
6
|
3.7 KB packages/playwright/src/util.ts
|
|
7
7
|
8.7 KB packages/playwright/src/worker/fixtureRunner.ts
|
|
8
8
|
21.3 KB packages/playwright/src/worker/testInfo.ts
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
5.4 KB packages/playwright/src/worker/timeoutManager.ts
|
|
11
11
|
0.3 KB packages/playwright/src/worker/util.ts
|
|
12
12
|
21.3 KB packages/playwright/src/worker/workerMain.ts
|
|
13
|
-
0
|
|
13
|
+
1.0 KB packages/playwright/src/worker/workerProcessEntry.ts
|
|
14
14
|
|
|
15
15
|
## External (7)
|
|
16
16
|
../codeCache
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flashwright",
|
|
3
|
-
"version": "1.64.1-flashwright.
|
|
3
|
+
"version": "1.64.1-flashwright.2",
|
|
4
4
|
"description": "Flashwright: a faster drop-in replacement for Playwright (fork of Playwright 1.64.1)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"./lib/common": "./lib/common/index.js",
|
|
23
23
|
"./lib/program": "./lib/program.js",
|
|
24
24
|
"./lib/runner/workerStandby": "./lib/runner/workerStandby.js",
|
|
25
|
+
"./lib/runner/session": "./lib/runner/session.js",
|
|
25
26
|
"./lib/codeCache": "./lib/codeCache.js",
|
|
26
27
|
"./lib/runner": "./lib/runner/index.js",
|
|
27
28
|
"./lib/transform/babelBundle": "./lib/transform/babelBundle.js",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
},
|
|
48
49
|
"license": "Apache-2.0",
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"playwright-core": "npm:flashwright-core@1.64.1-flashwright.
|
|
51
|
+
"playwright-core": "npm:flashwright-core@1.64.1-flashwright.2"
|
|
51
52
|
},
|
|
52
53
|
"bugs": {
|
|
53
54
|
"url": "https://github.com/xcodebeast/flashwright/issues"
|