Merge branch 'main' into expose-apiresponse-timing
|
|
@ -1710,6 +1710,11 @@ Step body.
|
||||||
|
|
||||||
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
|
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
|
||||||
|
|
||||||
|
### option: Test.step.location
|
||||||
|
* since: v1.48
|
||||||
|
- `location` <[Location]>
|
||||||
|
Specifies a custom location for the step to be shown in test reports. By default, location of the [`method: Test.step`] call is shown.
|
||||||
|
|
||||||
## method: Test.use
|
## method: Test.use
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
|
|
||||||
|
|
|
||||||
371
package-lock.json
generated
|
|
@ -61,7 +61,7 @@
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"ssim.js": "^3.5.0",
|
"ssim.js": "^3.5.0",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.3",
|
||||||
"vite": "^5.0.13",
|
"vite": "^5.4.6",
|
||||||
"ws": "^8.17.1",
|
"ws": "^8.17.1",
|
||||||
"xml2js": "^0.5.0",
|
"xml2js": "^0.5.0",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
|
|
@ -852,9 +852,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -1517,9 +1517,9 @@
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
|
||||||
"integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==",
|
"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1529,9 +1529,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
|
||||||
"integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==",
|
"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1541,9 +1541,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
|
||||||
"integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==",
|
"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1553,9 +1553,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
|
||||||
"integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==",
|
"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1565,9 +1565,21 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
|
||||||
"integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==",
|
"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
|
||||||
|
"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1577,9 +1589,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
|
||||||
"integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==",
|
"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1589,9 +1601,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
|
||||||
"integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==",
|
"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1601,11 +1613,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
|
||||||
"integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==",
|
"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64le"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1613,9 +1625,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
|
||||||
"integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==",
|
"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1625,9 +1637,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
|
||||||
"integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==",
|
"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -1637,9 +1649,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
|
||||||
"integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==",
|
"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1649,9 +1661,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
|
||||||
"integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==",
|
"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1661,9 +1673,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
|
||||||
"integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==",
|
"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1673,9 +1685,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
|
||||||
"integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==",
|
"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -1685,9 +1697,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
|
||||||
"integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==",
|
"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5862,9 +5874,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
|
|
@ -5917,9 +5929,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.38",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -5936,8 +5948,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.1.0",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
|
|
@ -6301,9 +6313,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.14.0",
|
"version": "4.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz",
|
||||||
"integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==",
|
"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.5"
|
"@types/estree": "1.0.5"
|
||||||
},
|
},
|
||||||
|
|
@ -6315,21 +6327,22 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.14.0",
|
"@rollup/rollup-android-arm-eabi": "4.21.3",
|
||||||
"@rollup/rollup-android-arm64": "4.14.0",
|
"@rollup/rollup-android-arm64": "4.21.3",
|
||||||
"@rollup/rollup-darwin-arm64": "4.14.0",
|
"@rollup/rollup-darwin-arm64": "4.21.3",
|
||||||
"@rollup/rollup-darwin-x64": "4.14.0",
|
"@rollup/rollup-darwin-x64": "4.21.3",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.14.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.21.3",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.14.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.21.3",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.14.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.21.3",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.0",
|
"@rollup/rollup-linux-arm64-musl": "4.21.3",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.14.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.14.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.21.3",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.14.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.21.3",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.14.0",
|
"@rollup/rollup-linux-x64-gnu": "4.21.3",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.14.0",
|
"@rollup/rollup-linux-x64-musl": "4.21.3",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.14.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.21.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.14.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.21.3",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.21.3",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -6589,9 +6602,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -7171,13 +7184,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.8",
|
"version": "5.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
|
||||||
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
|
"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.20.1",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.43",
|
||||||
"rollup": "^4.13.0"
|
"rollup": "^4.20.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
|
|
@ -7196,6 +7209,7 @@
|
||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.4.0"
|
||||||
|
|
@ -7213,6 +7227,9 @@
|
||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
|
@ -7243,9 +7260,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-arm": {
|
"node_modules/vite/node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -7258,9 +7275,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-arm64": {
|
"node_modules/vite/node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -7273,9 +7290,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-x64": {
|
"node_modules/vite/node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7288,9 +7305,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
|
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -7303,9 +7320,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
|
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7318,9 +7335,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -7333,9 +7350,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7348,9 +7365,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-arm": {
|
"node_modules/vite/node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -7363,9 +7380,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
|
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -7378,9 +7395,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
|
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -7393,9 +7410,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
|
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -7408,9 +7425,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
|
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
|
|
@ -7423,9 +7440,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
|
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -7438,9 +7455,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
|
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -7453,9 +7470,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
|
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -7468,9 +7485,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7483,9 +7500,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7498,9 +7515,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7513,9 +7530,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
|
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7528,9 +7545,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
|
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -7543,9 +7560,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
|
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -7558,9 +7575,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-x64": {
|
"node_modules/vite/node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -7573,9 +7590,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/esbuild": {
|
"node_modules/vite/node_modules/esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
|
|
@ -7584,29 +7601,29 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.5",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.5",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.5",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.5",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.5",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.5",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.5",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.5",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.5",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.5",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.5",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.5",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.5",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.5",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.5",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.5",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.5",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.5",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.5",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.5",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.5",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.5",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitefu": {
|
"node_modules/vitefu": {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"ssim.js": "^3.5.0",
|
"ssim.js": "^3.5.0",
|
||||||
"typescript": "^5.5.3",
|
"typescript": "^5.5.3",
|
||||||
"vite": "^5.0.13",
|
"vite": "^5.4.6",
|
||||||
"ws": "^8.17.1",
|
"ws": "^8.17.1",
|
||||||
"xml2js": "^0.5.0",
|
"xml2js": "^0.5.0",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree",
|
"name": "chromium-tip-of-tree",
|
||||||
"revision": "1259",
|
"revision": "1260",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "130.0.6713.0"
|
"browserVersion": "130.0.6723.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox",
|
"name": "firefox",
|
||||||
|
|
|
||||||
|
|
@ -567,10 +567,6 @@ async function codegen(options: Options & { target: string, output?: string, tes
|
||||||
tracesDir,
|
tracesDir,
|
||||||
});
|
});
|
||||||
dotenv.config({ path: 'playwright.env' });
|
dotenv.config({ path: 'playwright.env' });
|
||||||
if (process.env.PW_RECORDER_IS_TRACE_VIEWER) {
|
|
||||||
await fs.promises.mkdir(tracesDir, { recursive: true });
|
|
||||||
await context.tracing.start({ name: 'trace', _live: true });
|
|
||||||
}
|
|
||||||
await context._enableRecorder({
|
await context._enableRecorder({
|
||||||
language,
|
language,
|
||||||
launchOptions,
|
launchOptions,
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export class BidiConnection {
|
||||||
let context;
|
let context;
|
||||||
if ('context' in object.params)
|
if ('context' in object.params)
|
||||||
context = object.params.context;
|
context = object.params.context;
|
||||||
else if (object.method === 'log.entryAdded')
|
else if (object.method === 'log.entryAdded' || object.method === 'script.message')
|
||||||
context = object.params.source?.context;
|
context = object.params.source?.context;
|
||||||
if (context) {
|
if (context) {
|
||||||
const session = this._browsingContextToSession.get(context);
|
const session = this._browsingContextToSession.get(context);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { BidiSerializer } from './third_party/bidiSerializer';
|
||||||
|
|
||||||
export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
private readonly _session: BidiSession;
|
private readonly _session: BidiSession;
|
||||||
private readonly _target: bidi.Script.Target;
|
readonly _target: bidi.Script.Target;
|
||||||
|
|
||||||
constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) {
|
constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) {
|
||||||
this._session = session;
|
this._session = session;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ import type * as accessibility from '../accessibility';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import type * as frames from '../frames';
|
import type * as frames from '../frames';
|
||||||
import { type InitScript, Page, type PageDelegate } from '../page';
|
import { Page } from '../page';
|
||||||
|
import type { InitScript, PageDelegate } from '../page';
|
||||||
import type { Progress } from '../progress';
|
import type { Progress } from '../progress';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
import type { BidiBrowserContext } from './bidiBrowser';
|
import type { BidiBrowserContext } from './bidiBrowser';
|
||||||
|
|
@ -33,6 +34,7 @@ import { BidiNetworkManager } from './bidiNetworkManager';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
const kPlaywrightBindingChannel = 'playwrightChannel';
|
||||||
|
|
||||||
export class BidiPage implements PageDelegate {
|
export class BidiPage implements PageDelegate {
|
||||||
readonly rawMouse: RawMouseImpl;
|
readonly rawMouse: RawMouseImpl;
|
||||||
|
|
@ -62,6 +64,7 @@ export class BidiPage implements PageDelegate {
|
||||||
this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
|
this._page.on(Page.Events.FrameDetached, (frame: frames.Frame) => this._removeContextsForFrame(frame, false));
|
||||||
this._sessionListeners = [
|
this._sessionListeners = [
|
||||||
eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)),
|
eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)),
|
||||||
|
eventsHelper.addEventListener(bidiSession, 'script.message', this._onScriptMessage.bind(this)),
|
||||||
eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)),
|
eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)),
|
||||||
eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)),
|
eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)),
|
||||||
eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)),
|
eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)),
|
||||||
|
|
@ -93,6 +96,7 @@ export class BidiPage implements PageDelegate {
|
||||||
this.updateHttpCredentials(),
|
this.updateHttpCredentials(),
|
||||||
this.updateRequestInterception(),
|
this.updateRequestInterception(),
|
||||||
this._updateViewport(),
|
this._updateViewport(),
|
||||||
|
this._installMainBinding(),
|
||||||
this._addAllInitScripts(),
|
this._addAllInitScripts(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -315,18 +319,63 @@ export class BidiPage implements PageDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack(): Promise<boolean> {
|
async goBack(): Promise<boolean> {
|
||||||
throw new Error('Method not implemented.');
|
return await this._session.send('browsingContext.traverseHistory', {
|
||||||
|
context: this._session.sessionId,
|
||||||
|
delta: -1,
|
||||||
|
}).then(() => true).catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
goForward(): Promise<boolean> {
|
async goForward(): Promise<boolean> {
|
||||||
throw new Error('Method not implemented.');
|
return await this._session.send('browsingContext.traverseHistory', {
|
||||||
|
context: this._session.sessionId,
|
||||||
|
delta: +1,
|
||||||
|
}).then(() => true).catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async forceGarbageCollection(): Promise<void> {
|
async forceGarbageCollection(): Promise<void> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: consider calling this only when bindings are added.
|
||||||
|
private async _installMainBinding() {
|
||||||
|
const functionDeclaration = addMainBinding.toString();
|
||||||
|
const args: bidi.Script.ChannelValue[] = [{
|
||||||
|
type: 'channel',
|
||||||
|
value: {
|
||||||
|
channel: kPlaywrightBindingChannel,
|
||||||
|
ownership: bidi.Script.ResultOwnership.Root,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
const promises = [];
|
||||||
|
promises.push(this._session.send('script.addPreloadScript', {
|
||||||
|
functionDeclaration,
|
||||||
|
arguments: args,
|
||||||
|
}));
|
||||||
|
promises.push(this._session.send('script.callFunction', {
|
||||||
|
functionDeclaration,
|
||||||
|
arguments: args,
|
||||||
|
target: toBidiExecutionContext(await this._page.mainFrame()._mainContext())._target,
|
||||||
|
awaitPromise: false,
|
||||||
|
userActivation: false,
|
||||||
|
}));
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _onScriptMessage(event: bidi.Script.MessageParameters) {
|
||||||
|
if (event.channel !== kPlaywrightBindingChannel)
|
||||||
|
return;
|
||||||
|
const pageOrError = await this.pageOrError();
|
||||||
|
if (pageOrError instanceof Error)
|
||||||
|
return;
|
||||||
|
const context = this._realmToContext.get(event.source.realm);
|
||||||
|
if (!context)
|
||||||
|
return;
|
||||||
|
if (event.data.type !== 'string')
|
||||||
|
return;
|
||||||
|
await this._page._onBindingCalled(event.data.value, context);
|
||||||
|
}
|
||||||
|
|
||||||
async addInitScript(initScript: InitScript): Promise<void> {
|
async addInitScript(initScript: InitScript): Promise<void> {
|
||||||
const { script } = await this._session.send('script.addPreloadScript', {
|
const { script } = await this._session.send('script.addPreloadScript', {
|
||||||
// TODO: remove function call from the source.
|
// TODO: remove function call from the source.
|
||||||
|
|
@ -355,7 +404,20 @@ export class BidiPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
|
async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
|
||||||
throw new Error('Method not implemented.');
|
const rect = (documentRect || viewportRect)!;
|
||||||
|
const { data } = await this._session.send('browsingContext.captureScreenshot', {
|
||||||
|
context: this._session.sessionId,
|
||||||
|
format: {
|
||||||
|
type: `image/${format === 'png' ? 'png' : 'jpeg'}`,
|
||||||
|
quality: quality || 80,
|
||||||
|
},
|
||||||
|
origin: documentRect ? 'document' : 'viewport',
|
||||||
|
clip: {
|
||||||
|
type: 'box',
|
||||||
|
...rect,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Buffer.from(data, 'base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
|
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
|
||||||
|
|
@ -522,6 +584,10 @@ export class BidiPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addMainBinding(callback: (arg: any) => void) {
|
||||||
|
(globalThis as any)['__playwright__binding__'] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
function toBidiExecutionContext(executionContext: dom.FrameExecutionContext): BidiExecutionContext {
|
function toBidiExecutionContext(executionContext: dom.FrameExecutionContext): BidiExecutionContext {
|
||||||
return (executionContext as any)[contextDelegateSymbol] as BidiExecutionContext;
|
return (executionContext as any)[contextDelegateSymbol] as BidiExecutionContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import type * as types from '../types';
|
||||||
import type { ActionInContext, LanguageGenerator, LanguageGeneratorOptions } from './types';
|
import type { ActionInContext, LanguageGenerator, LanguageGeneratorOptions } from './types';
|
||||||
|
|
||||||
export function generateCode(actions: ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) {
|
export function generateCode(actions: ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) {
|
||||||
actions = collapseActions(actions);
|
|
||||||
const header = languageGenerator.generateHeader(options);
|
const header = languageGenerator.generateHeader(options);
|
||||||
const footer = languageGenerator.generateFooter(options.saveStorage);
|
const footer = languageGenerator.generateFooter(options.saveStorage);
|
||||||
const actionTexts = actions.map(a => languageGenerator.generateAction(a)).filter(Boolean);
|
const actionTexts = actions.map(a => languageGenerator.generateAction(a)).filter(Boolean);
|
||||||
|
|
@ -70,6 +69,23 @@ export function toKeyboardModifiers(modifiers: number): types.SmartKeyboardModif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fromKeyboardModifiers(modifiers?: types.SmartKeyboardModifier[]): number {
|
||||||
|
let result = 0;
|
||||||
|
if (!modifiers)
|
||||||
|
return result;
|
||||||
|
if (modifiers.includes('Alt'))
|
||||||
|
result |= 1;
|
||||||
|
if (modifiers.includes('Control'))
|
||||||
|
result |= 2;
|
||||||
|
if (modifiers.includes('ControlOrMeta'))
|
||||||
|
result |= 2;
|
||||||
|
if (modifiers.includes('Meta'))
|
||||||
|
result |= 4;
|
||||||
|
if (modifiers.includes('Shift'))
|
||||||
|
result |= 8;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function toClickOptionsForSourceCode(action: actions.ClickAction): types.MouseClickOptions {
|
export function toClickOptionsForSourceCode(action: actions.ClickAction): types.MouseClickOptions {
|
||||||
const modifiers = toKeyboardModifiers(action.modifiers);
|
const modifiers = toKeyboardModifiers(action.modifiers);
|
||||||
const options: types.MouseClickOptions = {};
|
const options: types.MouseClickOptions = {};
|
||||||
|
|
@ -84,19 +100,3 @@ export function toClickOptionsForSourceCode(action: actions.ClickAction): types.
|
||||||
options.position = action.position;
|
options.position = action.position;
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
function collapseActions(actions: ActionInContext[]): ActionInContext[] {
|
|
||||||
const result: ActionInContext[] = [];
|
|
||||||
for (const action of actions) {
|
|
||||||
const lastAction = result[result.length - 1];
|
|
||||||
const isSameAction = lastAction && lastAction.action.name === action.action.name && lastAction.frame.pageAlias === action.frame.pageAlias && lastAction.frame.framePath.join('|') === action.frame.framePath.join('|');
|
|
||||||
const isSameSelector = lastAction && 'selector' in lastAction.action && 'selector' in action.action && action.action.selector === lastAction.action.selector;
|
|
||||||
const shouldMerge = isSameAction && (action.action.name === 'navigate' || (action.action.name === 'fill' && isSameSelector));
|
|
||||||
if (!shouldMerge) {
|
|
||||||
result.push(action);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result[result.length - 1] = action;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import { serializeError } from '../errors';
|
||||||
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
|
import { ElementHandleDispatcher } from './elementHandlerDispatcher';
|
||||||
import { RecorderInTraceViewer } from '../recorder/recorderInTraceViewer';
|
import { RecorderInTraceViewer } from '../recorder/recorderInTraceViewer';
|
||||||
import { RecorderApp } from '../recorder/recorderApp';
|
import { RecorderApp } from '../recorder/recorderApp';
|
||||||
|
import type { IRecorderAppFactory } from '../recorder/recorderFrontend';
|
||||||
|
|
||||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
|
||||||
_type_EventTarget = true;
|
_type_EventTarget = true;
|
||||||
|
|
@ -293,7 +294,20 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||||
}
|
}
|
||||||
|
|
||||||
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
|
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
|
||||||
const factory = process.env.PW_RECORDER_IS_TRACE_VIEWER ? RecorderInTraceViewer.factory(this._context) : RecorderApp.factory(this._context);
|
let factory: IRecorderAppFactory;
|
||||||
|
if (process.env.PW_RECORDER_IS_TRACE_VIEWER) {
|
||||||
|
factory = RecorderInTraceViewer.factory(this._context);
|
||||||
|
await this._context.tracing.start({
|
||||||
|
name: 'trace',
|
||||||
|
snapshots: true,
|
||||||
|
screenshots: false,
|
||||||
|
live: true,
|
||||||
|
inMemory: true,
|
||||||
|
});
|
||||||
|
await this._context.tracing.startChunk({ name: 'trace', title: 'trace' });
|
||||||
|
} else {
|
||||||
|
factory = RecorderApp.factory(this._context);
|
||||||
|
}
|
||||||
await Recorder.show(this._context, factory, params);
|
await Recorder.show(this._context, factory, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import type * as types from './types';
|
||||||
import type { HeadersArray, ProxySettings } from './types';
|
import type { HeadersArray, ProxySettings } from './types';
|
||||||
import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor';
|
import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor';
|
||||||
import type * as har from '@trace/har';
|
import type * as har from '@trace/har';
|
||||||
|
import { TLSSocket } from 'tls';
|
||||||
|
|
||||||
type FetchRequestOptions = {
|
type FetchRequestOptions = {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
|
|
@ -73,6 +74,9 @@ export type APIRequestFinishedEvent = {
|
||||||
statusMessage: string;
|
statusMessage: string;
|
||||||
body?: Buffer;
|
body?: Buffer;
|
||||||
timings: har.Timings;
|
timings: har.Timings;
|
||||||
|
serverIPAddress?: string;
|
||||||
|
serverPort?: number;
|
||||||
|
securityDetails?: har.SecurityDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SendRequestOptions = https.RequestOptions & {
|
type SendRequestOptions = https.RequestOptions & {
|
||||||
|
|
@ -302,6 +306,10 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
let tcpConnectionAt: number | undefined;
|
let tcpConnectionAt: number | undefined;
|
||||||
let tlsHandshakeAt: number | undefined;
|
let tlsHandshakeAt: number | undefined;
|
||||||
let requestFinishAt: number | undefined;
|
let requestFinishAt: number | undefined;
|
||||||
|
let serverIPAddress: string | undefined;
|
||||||
|
let serverPort: number | undefined;
|
||||||
|
|
||||||
|
let securityDetails: har.SecurityDetails | undefined;
|
||||||
|
|
||||||
const request = requestConstructor(url, requestOptions as any, async response => {
|
const request = requestConstructor(url, requestOptions as any, async response => {
|
||||||
const responseAt = monotonicTime();
|
const responseAt = monotonicTime();
|
||||||
|
|
@ -328,6 +336,9 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
cookies,
|
cookies,
|
||||||
body,
|
body,
|
||||||
timings,
|
timings,
|
||||||
|
serverIPAddress,
|
||||||
|
serverPort,
|
||||||
|
securityDetails,
|
||||||
};
|
};
|
||||||
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
||||||
};
|
};
|
||||||
|
|
@ -503,10 +514,26 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
// non-happy-eyeballs sockets
|
// non-happy-eyeballs sockets
|
||||||
socket.on('lookup', () => { dnsLookupAt = monotonicTime(); });
|
socket.on('lookup', () => { dnsLookupAt = monotonicTime(); });
|
||||||
socket.on('connect', () => { tcpConnectionAt = monotonicTime(); });
|
socket.on('connect', () => { tcpConnectionAt = monotonicTime(); });
|
||||||
socket.on('secureConnect', () => { tlsHandshakeAt = monotonicTime(); });
|
socket.on('secureConnect', () => {
|
||||||
|
tlsHandshakeAt = monotonicTime();
|
||||||
|
|
||||||
|
if (socket instanceof TLSSocket) {
|
||||||
|
const peerCertificate = socket.getPeerCertificate();
|
||||||
|
securityDetails = {
|
||||||
|
protocol: socket.getProtocol() ?? undefined,
|
||||||
|
subjectName: peerCertificate.subject.CN,
|
||||||
|
validFrom: new Date(peerCertificate.valid_from).getTime() / 1000,
|
||||||
|
validTo: new Date(peerCertificate.valid_to).getTime() / 1000,
|
||||||
|
issuer: peerCertificate.issuer.CN
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// socks / http proxy
|
// socks / http proxy
|
||||||
socket.on('proxyConnect', () => { tcpConnectionAt = monotonicTime(); });
|
socket.on('proxyConnect', () => { tcpConnectionAt = monotonicTime(); });
|
||||||
|
|
||||||
|
serverIPAddress = socket.remoteAddress;
|
||||||
|
serverPort = socket.remotePort;
|
||||||
});
|
});
|
||||||
request.on('finish', () => { requestFinishAt = monotonicTime(); });
|
request.on('finish', () => { requestFinishAt = monotonicTime(); });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,11 +213,19 @@ export class HarTracer {
|
||||||
harEntry.response.httpVersion = event.httpVersion;
|
harEntry.response.httpVersion = event.httpVersion;
|
||||||
harEntry.response.redirectURL = event.headers.location || '';
|
harEntry.response.redirectURL = event.headers.location || '';
|
||||||
|
|
||||||
|
if (!this._options.omitServerIP) {
|
||||||
|
harEntry.serverIPAddress = event.serverIPAddress;
|
||||||
|
harEntry._serverPort = event.serverPort;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._options.omitTiming) {
|
if (!this._options.omitTiming) {
|
||||||
harEntry.timings = event.timings;
|
harEntry.timings = event.timings;
|
||||||
this._computeHarEntryTotalTime(harEntry);
|
this._computeHarEntryTotalTime(harEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._options.omitSecurityDetails)
|
||||||
|
harEntry._securityDetails = event.securityDetails;
|
||||||
|
|
||||||
for (let i = 0; i < event.rawHeaders.length; i += 2) {
|
for (let i = 0; i < event.rawHeaders.length; i += 2) {
|
||||||
harEntry.response.headers.push({
|
harEntry.response.headers.push({
|
||||||
name: event.rawHeaders[i],
|
name: event.rawHeaders[i],
|
||||||
|
|
@ -236,6 +244,8 @@ export class HarTracer {
|
||||||
if (contentType)
|
if (contentType)
|
||||||
content.mimeType = contentType;
|
content.mimeType = contentType;
|
||||||
this._storeResponseContent(event.body, content, 'other');
|
this._storeResponseContent(event.body, content, 'other');
|
||||||
|
if (!this._options.omitSizes)
|
||||||
|
harEntry.response.bodySize = event.body?.length ?? 0;
|
||||||
|
|
||||||
if (this._started)
|
if (this._started)
|
||||||
this._delegate.onEntryFinished(harEntry);
|
this._delegate.onEntryFinished(harEntry);
|
||||||
|
|
|
||||||
|
|
@ -1039,9 +1039,12 @@ export class Recorder {
|
||||||
|
|
||||||
this.highlight.install();
|
this.highlight.install();
|
||||||
// some frameworks erase the DOM on hydration, this ensures it's reattached
|
// some frameworks erase the DOM on hydration, this ensures it's reattached
|
||||||
const recreationInterval = setInterval(() => {
|
let recreationInterval: number | undefined;
|
||||||
|
const recreate = () => {
|
||||||
this.highlight.install();
|
this.highlight.install();
|
||||||
}, 500);
|
recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500);
|
||||||
|
};
|
||||||
|
recreationInterval = this.injectedScript.builtinSetTimeout(recreate, 500);
|
||||||
this._listeners.push(() => clearInterval(recreationInterval));
|
this._listeners.push(() => clearInterval(recreationInterval));
|
||||||
|
|
||||||
this.overlay?.install();
|
this.overlay?.install();
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ export class ContextRecorder extends EventEmitter {
|
||||||
saveStorage: params.saveStorage,
|
saveStorage: params.saveStorage,
|
||||||
};
|
};
|
||||||
|
|
||||||
const collection = new RecorderCollection(this._pageAliases, params.mode === 'recording');
|
const collection = new RecorderCollection(context, this._pageAliases, params.mode === 'recording');
|
||||||
collection.on('change', () => {
|
collection.on('change', () => {
|
||||||
this._recorderSources = [];
|
this._recorderSources = [];
|
||||||
for (const languageGenerator of this._orderedLanguages) {
|
for (const languageGenerator of this._orderedLanguages) {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
|
wsEndpointForTest: undefined;
|
||||||
async close(): Promise<void> {}
|
async close(): Promise<void> {}
|
||||||
async setPaused(paused: boolean): Promise<void> {}
|
async setPaused(paused: boolean): Promise<void> {}
|
||||||
async setMode(mode: Mode): Promise<void> {}
|
async setMode(mode: Mode): Promise<void> {}
|
||||||
|
|
@ -54,7 +55,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
|
|
||||||
export class RecorderApp extends EventEmitter implements IRecorderApp {
|
export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
readonly wsEndpoint: string | undefined;
|
readonly wsEndpointForTest: string | undefined;
|
||||||
private _recorder: IRecorder;
|
private _recorder: IRecorder;
|
||||||
|
|
||||||
constructor(recorder: IRecorder, page: Page, wsEndpoint: string | undefined) {
|
constructor(recorder: IRecorder, page: Page, wsEndpoint: string | undefined) {
|
||||||
|
|
@ -62,7 +63,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
this.setMaxListeners(0);
|
this.setMaxListeners(0);
|
||||||
this._recorder = recorder;
|
this._recorder = recorder;
|
||||||
this._page = page;
|
this._page = page;
|
||||||
this.wsEndpoint = wsEndpoint;
|
this.wsEndpointForTest = wsEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
|
|
|
||||||
|
|
@ -20,31 +20,35 @@ import type { Page } from '../page';
|
||||||
import type { Signal } from './recorderActions';
|
import type { Signal } from './recorderActions';
|
||||||
import type { ActionInContext } from '../codegen/types';
|
import type { ActionInContext } from '../codegen/types';
|
||||||
import { monotonicTime } from '../../utils/time';
|
import { monotonicTime } from '../../utils/time';
|
||||||
import { callMetadataForAction } from './recorderUtils';
|
import { callMetadataForAction, collapseActions, traceEventsToAction } from './recorderUtils';
|
||||||
import { serializeError } from '../errors';
|
import { serializeError } from '../errors';
|
||||||
import { performAction } from './recorderRunner';
|
import { performAction } from './recorderRunner';
|
||||||
import type { CallMetadata } from '@protocol/callMetadata';
|
import type { CallMetadata } from '@protocol/callMetadata';
|
||||||
import { isUnderTest } from '../../utils/debug';
|
import { isUnderTest } from '../../utils/debug';
|
||||||
|
import type { BrowserContext } from '../browserContext';
|
||||||
|
|
||||||
export class RecorderCollection extends EventEmitter {
|
export class RecorderCollection extends EventEmitter {
|
||||||
private _actions: ActionInContext[] = [];
|
private _actions: ActionInContext[] = [];
|
||||||
private _enabled: boolean;
|
private _enabled: boolean;
|
||||||
private _pageAliases: Map<Page, string>;
|
private _pageAliases: Map<Page, string>;
|
||||||
|
private _context: BrowserContext;
|
||||||
|
|
||||||
constructor(pageAliases: Map<Page, string>, enabled: boolean) {
|
constructor(context: BrowserContext, pageAliases: Map<Page, string>, enabled: boolean) {
|
||||||
super();
|
super();
|
||||||
|
this._context = context;
|
||||||
this._enabled = enabled;
|
this._enabled = enabled;
|
||||||
this._pageAliases = pageAliases;
|
this._pageAliases = pageAliases;
|
||||||
this.restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
this._actions = [];
|
this._actions = [];
|
||||||
this.emit('change');
|
this._fireChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
actions() {
|
actions() {
|
||||||
return this._actions;
|
if (!process.env.PW_RECORDER_IS_TRACE_VIEWER)
|
||||||
|
return collapseActions(this._actions);
|
||||||
|
return collapseActions(traceEventsToAction(this._context.tracing.inMemoryEvents()));
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnabled(enabled: boolean) {
|
setEnabled(enabled: boolean) {
|
||||||
|
|
@ -60,7 +64,7 @@ export class RecorderCollection extends EventEmitter {
|
||||||
addRecordedAction(actionInContext: ActionInContext) {
|
addRecordedAction(actionInContext: ActionInContext) {
|
||||||
if (['openPage', 'closePage'].includes(actionInContext.action.name)) {
|
if (['openPage', 'closePage'].includes(actionInContext.action.name)) {
|
||||||
this._actions.push(actionInContext);
|
this._actions.push(actionInContext);
|
||||||
this.emit('change');
|
this._fireChange();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._addAction(actionInContext).catch(() => {});
|
this._addAction(actionInContext).catch(() => {});
|
||||||
|
|
@ -69,11 +73,16 @@ export class RecorderCollection extends EventEmitter {
|
||||||
private async _addAction(actionInContext: ActionInContext, callback?: (callMetadata: CallMetadata) => Promise<void>) {
|
private async _addAction(actionInContext: ActionInContext, callback?: (callMetadata: CallMetadata) => Promise<void>) {
|
||||||
if (!this._enabled)
|
if (!this._enabled)
|
||||||
return;
|
return;
|
||||||
|
if (actionInContext.action.name === 'openPage' || actionInContext.action.name === 'closePage') {
|
||||||
|
this._actions.push(actionInContext);
|
||||||
|
this._fireChange();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { callMetadata, mainFrame } = callMetadataForAction(this._pageAliases, actionInContext);
|
const { callMetadata, mainFrame } = callMetadataForAction(this._pageAliases, actionInContext);
|
||||||
await mainFrame.instrumentation.onBeforeCall(mainFrame, callMetadata);
|
await mainFrame.instrumentation.onBeforeCall(mainFrame, callMetadata);
|
||||||
this._actions.push(actionInContext);
|
this._actions.push(actionInContext);
|
||||||
this.emit('change');
|
this._fireChange();
|
||||||
const error = await callback?.(callMetadata).catch((e: Error) => e);
|
const error = await callback?.(callMetadata).catch((e: Error) => e);
|
||||||
callMetadata.endTime = monotonicTime();
|
callMetadata.endTime = monotonicTime();
|
||||||
callMetadata.error = error ? serializeError(error) : undefined;
|
callMetadata.error = error ? serializeError(error) : undefined;
|
||||||
|
|
@ -120,4 +129,8 @@ export class RecorderCollection extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _fireChange() {
|
||||||
|
this.emit('change');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export interface IRecorder {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRecorderApp extends EventEmitter {
|
export interface IRecorderApp extends EventEmitter {
|
||||||
|
readonly wsEndpointForTest: string | undefined;
|
||||||
close(): Promise<void>;
|
close(): Promise<void>;
|
||||||
setPaused(paused: boolean): Promise<void>;
|
setPaused(paused: boolean): Promise<void>;
|
||||||
setMode(mode: Mode): Promise<void>;
|
setMode(mode: Mode): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { gracefullyProcessExitDoNotHang } from '../../utils/processLauncher';
|
||||||
import type { Transport } from '../../utils/httpServer';
|
import type { Transport } from '../../utils/httpServer';
|
||||||
|
|
||||||
export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
|
export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp {
|
||||||
|
readonly wsEndpointForTest: string | undefined;
|
||||||
private _recorder: IRecorder;
|
private _recorder: IRecorder;
|
||||||
private _transport: Transport;
|
private _transport: Transport;
|
||||||
|
|
||||||
|
|
@ -32,15 +33,16 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp
|
||||||
return async (recorder: IRecorder) => {
|
return async (recorder: IRecorder) => {
|
||||||
const transport = new RecorderTransport();
|
const transport = new RecorderTransport();
|
||||||
const trace = path.join(context._browser.options.tracesDir, 'trace');
|
const trace = path.join(context._browser.options.tracesDir, 'trace');
|
||||||
await openApp(trace, { transport });
|
const wsEndpointForTest = await openApp(trace, { transport, headless: !context._browser.options.headful });
|
||||||
return new RecorderInTraceViewer(context, recorder, transport);
|
return new RecorderInTraceViewer(context, recorder, transport, wsEndpointForTest);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: BrowserContext, recorder: IRecorder, transport: Transport) {
|
constructor(context: BrowserContext, recorder: IRecorder, transport: Transport, wsEndpointForTest: string | undefined) {
|
||||||
super();
|
super();
|
||||||
this._recorder = recorder;
|
this._recorder = recorder;
|
||||||
this._transport = transport;
|
this._transport = transport;
|
||||||
|
this.wsEndpointForTest = wsEndpointForTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
|
|
@ -72,11 +74,12 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }) {
|
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<string | undefined> {
|
||||||
const server = await startTraceViewerServer(options);
|
const server = await startTraceViewerServer(options);
|
||||||
await installRootRedirect(server, [trace], { ...options, webApp: 'recorder.html' });
|
await installRootRedirect(server, [trace], { ...options, webApp: 'recorder.html' });
|
||||||
const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', options);
|
const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', options);
|
||||||
page.on('close', () => gracefullyProcessExitDoNotHang(0));
|
page.on('close', () => gracefullyProcessExitDoNotHang(0));
|
||||||
|
return page.context()._browser.options.wsEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecorderTransport implements Transport {
|
class RecorderTransport implements Transport {
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,12 @@ import type { Page } from '../page';
|
||||||
import type { ActionInContext } from '../codegen/types';
|
import type { ActionInContext } from '../codegen/types';
|
||||||
import type { Frame } from '../frames';
|
import type { Frame } from '../frames';
|
||||||
import type * as actions from './recorderActions';
|
import type * as actions from './recorderActions';
|
||||||
import { toKeyboardModifiers } from '../codegen/language';
|
import type * as channels from '@protocol/channels';
|
||||||
|
import type * as trace from '@trace/trace';
|
||||||
|
import { fromKeyboardModifiers, toKeyboardModifiers } from '../codegen/language';
|
||||||
import { serializeExpectedTextValues } from '../../utils/expectUtils';
|
import { serializeExpectedTextValues } from '../../utils/expectUtils';
|
||||||
import { createGuid, monotonicTime } from '../../utils';
|
import { createGuid, monotonicTime } from '../../utils';
|
||||||
|
import { serializeValue } from '../../protocol/serializers';
|
||||||
|
|
||||||
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
|
||||||
let title = metadata.apiName || metadata.method;
|
let title = metadata.apiName || metadata.method;
|
||||||
|
|
@ -76,57 +79,113 @@ export async function frameForAction(pageAliases: Map<Page, string>, actionInCon
|
||||||
return result.frame;
|
return result.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function traceParamsForAction(actionInContext: ActionInContext) {
|
export function traceParamsForAction(actionInContext: ActionInContext): { method: string, params: any } {
|
||||||
const { action } = actionInContext;
|
const { action } = actionInContext;
|
||||||
|
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'navigate': return { url: action.url };
|
case 'navigate': {
|
||||||
case 'openPage': return {};
|
const params: channels.FrameGotoParams = {
|
||||||
case 'closePage': return {};
|
url: action.url,
|
||||||
|
};
|
||||||
|
return { method: 'goto', params };
|
||||||
|
}
|
||||||
|
case 'openPage':
|
||||||
|
case 'closePage':
|
||||||
|
throw new Error('Not reached');
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = buildFullSelector(actionInContext.frame.framePath, action.selector);
|
const selector = buildFullSelector(actionInContext.frame.framePath, action.selector);
|
||||||
switch (action.name) {
|
switch (action.name) {
|
||||||
case 'click': return { selector, clickCount: action.clickCount };
|
case 'click': {
|
||||||
case 'press': {
|
const params: channels.FrameClickParams = {
|
||||||
const modifiers = toKeyboardModifiers(action.modifiers);
|
|
||||||
const shortcut = [...modifiers, action.key].join('+');
|
|
||||||
return { selector, key: shortcut };
|
|
||||||
}
|
|
||||||
case 'fill': return { selector, text: action.text };
|
|
||||||
case 'setInputFiles': return { selector, files: action.files };
|
|
||||||
case 'check': return { selector };
|
|
||||||
case 'uncheck': return { selector };
|
|
||||||
case 'select': return { selector, values: action.options.map(value => ({ value })) };
|
|
||||||
case 'assertChecked': {
|
|
||||||
return {
|
|
||||||
selector,
|
selector,
|
||||||
expression: 'to.be.checked',
|
strict: true,
|
||||||
isNot: !action.checked,
|
modifiers: toKeyboardModifiers(action.modifiers),
|
||||||
|
button: action.button,
|
||||||
|
clickCount: action.clickCount,
|
||||||
|
position: action.position,
|
||||||
};
|
};
|
||||||
|
return { method: 'click', params };
|
||||||
|
}
|
||||||
|
case 'press': {
|
||||||
|
const params: channels.FramePressParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
key: [...toKeyboardModifiers(action.modifiers), action.key].join('+'),
|
||||||
|
};
|
||||||
|
return { method: 'press', params };
|
||||||
|
}
|
||||||
|
case 'fill': {
|
||||||
|
const params: channels.FrameFillParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
value: action.text,
|
||||||
|
};
|
||||||
|
return { method: 'fill', params };
|
||||||
|
}
|
||||||
|
case 'setInputFiles': {
|
||||||
|
const params: channels.FrameSetInputFilesParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
localPaths: action.files,
|
||||||
|
};
|
||||||
|
return { method: 'setInputFiles', params };
|
||||||
|
}
|
||||||
|
case 'check': {
|
||||||
|
const params: channels.FrameCheckParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
};
|
||||||
|
return { method: 'check', params };
|
||||||
|
}
|
||||||
|
case 'uncheck': {
|
||||||
|
const params: channels.FrameUncheckParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
};
|
||||||
|
return { method: 'uncheck', params };
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
const params: channels.FrameSelectOptionParams = {
|
||||||
|
selector,
|
||||||
|
strict: true,
|
||||||
|
options: action.options.map(option => ({ value: option })),
|
||||||
|
};
|
||||||
|
return { method: 'selectOption', params };
|
||||||
|
}
|
||||||
|
case 'assertChecked': {
|
||||||
|
const params: channels.FrameExpectParams = {
|
||||||
|
selector: action.selector,
|
||||||
|
expression: 'to.be.checked',
|
||||||
|
isNot: action.checked,
|
||||||
|
};
|
||||||
|
return { method: 'expect', params };
|
||||||
}
|
}
|
||||||
case 'assertText': {
|
case 'assertText': {
|
||||||
return {
|
const params: channels.FrameExpectParams = {
|
||||||
selector,
|
selector,
|
||||||
expression: 'to.have.text',
|
expression: 'to.have.text',
|
||||||
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }),
|
expectedText: serializeExpectedTextValues([action.text], { matchSubstring: true, normalizeWhiteSpace: true }),
|
||||||
isNot: false,
|
isNot: false,
|
||||||
};
|
};
|
||||||
|
return { method: 'expect', params };
|
||||||
}
|
}
|
||||||
case 'assertValue': {
|
case 'assertValue': {
|
||||||
return {
|
const params: channels.FrameExpectParams = {
|
||||||
selector,
|
selector,
|
||||||
expression: 'to.have.value',
|
expression: 'to.have.value',
|
||||||
expectedValue: action.value,
|
expectedValue: { value: serializeValue(action.value, value => ({ fallThrough: value })), handles: [] },
|
||||||
isNot: false,
|
isNot: false,
|
||||||
};
|
};
|
||||||
|
return { method: 'expect', params };
|
||||||
}
|
}
|
||||||
case 'assertVisible': {
|
case 'assertVisible': {
|
||||||
return {
|
const params: channels.FrameExpectParams = {
|
||||||
selector,
|
selector,
|
||||||
expression: 'to.be.visible',
|
expression: 'to.be.visible',
|
||||||
isNot: false,
|
isNot: false,
|
||||||
};
|
};
|
||||||
|
return { method: 'expect', params };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +193,10 @@ export function traceParamsForAction(actionInContext: ActionInContext) {
|
||||||
export function callMetadataForAction(pageAliases: Map<Page, string>, actionInContext: ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } {
|
export function callMetadataForAction(pageAliases: Map<Page, string>, actionInContext: ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } {
|
||||||
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
||||||
const { action } = actionInContext;
|
const { action } = actionInContext;
|
||||||
|
const { method, params } = traceParamsForAction(actionInContext);
|
||||||
const callMetadata: CallMetadata = {
|
const callMetadata: CallMetadata = {
|
||||||
id: `call@${createGuid()}`,
|
id: `call@${createGuid()}`,
|
||||||
|
stepId: `recorder@${createGuid()}`,
|
||||||
apiName: 'frame.' + action.name,
|
apiName: 'frame.' + action.name,
|
||||||
objectId: mainFrame.guid,
|
objectId: mainFrame.guid,
|
||||||
pageId: mainFrame._page.guid,
|
pageId: mainFrame._page.guid,
|
||||||
|
|
@ -143,9 +204,132 @@ export function callMetadataForAction(pageAliases: Map<Page, string>, actionInCo
|
||||||
startTime: monotonicTime(),
|
startTime: monotonicTime(),
|
||||||
endTime: 0,
|
endTime: 0,
|
||||||
type: 'Frame',
|
type: 'Frame',
|
||||||
method: action.name,
|
method,
|
||||||
params: traceParamsForAction(actionInContext),
|
params,
|
||||||
log: [],
|
log: [],
|
||||||
};
|
};
|
||||||
return { callMetadata, mainFrame };
|
return { callMetadata, mainFrame };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext[] {
|
||||||
|
const result: ActionInContext[] = [];
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.type !== 'before')
|
||||||
|
continue;
|
||||||
|
if (!event.stepId?.startsWith('recorder@'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (event.method === 'goto') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'navigate',
|
||||||
|
url: event.params.url,
|
||||||
|
signals: [],
|
||||||
|
},
|
||||||
|
timestamp: event.startTime,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'click') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'click',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
button: event.params.button,
|
||||||
|
modifiers: fromKeyboardModifiers(event.params.modifiers),
|
||||||
|
clickCount: event.params.clickCount,
|
||||||
|
position: event.params.position,
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'fill') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'fill',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
text: event.params.value,
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'press') {
|
||||||
|
const tokens = event.params.key.split('+');
|
||||||
|
const modifiers = tokens.slice(0, tokens.length - 1);
|
||||||
|
const key = tokens[tokens.length - 1];
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'press',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
key,
|
||||||
|
modifiers: fromKeyboardModifiers(modifiers),
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'check') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'check',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'uncheck') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'uncheck',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (event.method === 'selectOption') {
|
||||||
|
result.push({
|
||||||
|
frame: { pageAlias: 'page', framePath: [] },
|
||||||
|
action: {
|
||||||
|
name: 'select',
|
||||||
|
selector: event.params.selector,
|
||||||
|
signals: [],
|
||||||
|
options: event.params.options.map((option: any) => option.value),
|
||||||
|
},
|
||||||
|
timestamp: event.startTime
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collapseActions(actions: ActionInContext[]): ActionInContext[] {
|
||||||
|
const result: ActionInContext[] = [];
|
||||||
|
for (const action of actions) {
|
||||||
|
const lastAction = result[result.length - 1];
|
||||||
|
const isSameAction = lastAction && lastAction.action.name === action.action.name && lastAction.frame.pageAlias === action.frame.pageAlias && lastAction.frame.framePath.join('|') === action.frame.framePath.join('|');
|
||||||
|
const isSameSelector = lastAction && 'selector' in lastAction.action && 'selector' in action.action && action.action.selector === lastAction.action.selector;
|
||||||
|
const shouldMerge = isSameAction && (action.action.name === 'navigate' || (action.action.name === 'fill' && isSameSelector));
|
||||||
|
if (!shouldMerge) {
|
||||||
|
result.push(action);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result[result.length - 1] = action;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ export type TracerOptions = {
|
||||||
snapshots?: boolean;
|
snapshots?: boolean;
|
||||||
screenshots?: boolean;
|
screenshots?: boolean;
|
||||||
live?: boolean;
|
live?: boolean;
|
||||||
|
inMemory?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RecordingState = {
|
type RecordingState = {
|
||||||
|
|
@ -79,6 +80,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
private _allResources = new Set<string>();
|
private _allResources = new Set<string>();
|
||||||
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
||||||
private _pendingHarEntries = new Set<har.Entry>();
|
private _pendingHarEntries = new Set<har.Entry>();
|
||||||
|
private _inMemoryEvents: trace.TraceEvent[] | undefined;
|
||||||
|
|
||||||
constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) {
|
constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) {
|
||||||
super(context, 'tracing');
|
super(context, 'tracing');
|
||||||
|
|
@ -153,6 +155,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
// Tracing is 10x bigger if we include scripts in every trace.
|
// Tracing is 10x bigger if we include scripts in every trace.
|
||||||
if (options.snapshots)
|
if (options.snapshots)
|
||||||
this._harTracer.start({ omitScripts: !options.live });
|
this._harTracer.start({ omitScripts: !options.live });
|
||||||
|
this._inMemoryEvents = options.inMemory ? [] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async startChunk(options: { name?: string, title?: string } = {}): Promise<{ traceName: string }> {
|
async startChunk(options: { name?: string, title?: string } = {}): Promise<{ traceName: string }> {
|
||||||
|
|
@ -179,7 +182,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
wallTime: Date.now(),
|
wallTime: Date.now(),
|
||||||
monotonicTime: monotonicTime()
|
monotonicTime: monotonicTime()
|
||||||
};
|
};
|
||||||
this._fs.appendFile(this._state.traceFile, JSON.stringify(event) + '\n');
|
this._appendTraceEvent(event);
|
||||||
|
|
||||||
this._context.instrumentation.addListener(this, this._context);
|
this._context.instrumentation.addListener(this, this._context);
|
||||||
this._eventListeners.push(
|
this._eventListeners.push(
|
||||||
|
|
@ -193,6 +196,10 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
return { traceName: this._state.traceName };
|
return { traceName: this._state.traceName };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inMemoryEvents(): trace.TraceEvent[] {
|
||||||
|
return this._inMemoryEvents || [];
|
||||||
|
}
|
||||||
|
|
||||||
private _startScreencast() {
|
private _startScreencast() {
|
||||||
if (!(this._context instanceof BrowserContext))
|
if (!(this._context instanceof BrowserContext))
|
||||||
return;
|
return;
|
||||||
|
|
@ -487,6 +494,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
// Do not flush (console) events, they are too noisy, unless we are in ui mode (live).
|
// Do not flush (console) events, they are too noisy, unless we are in ui mode (live).
|
||||||
const flush = this._state!.options.live || (event.type !== 'event' && event.type !== 'console' && event.type !== 'log');
|
const flush = this._state!.options.live || (event.type !== 'event' && event.type !== 'console' && event.type !== 'log');
|
||||||
this._fs.appendFile(this._state!.traceFile, JSON.stringify(visited) + '\n', flush);
|
this._fs.appendFile(this._state!.traceFile, JSON.stringify(visited) + '\n', flush);
|
||||||
|
if (this._inMemoryEvents)
|
||||||
|
this._inMemoryEvents.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _appendResource(sha1: string, buffer: Buffer) {
|
private _appendResource(sha1: string, buffer: Buffer) {
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,7 @@ export async function openTraceViewerApp(url: string, browserName: string, optio
|
||||||
...options?.persistentContextOptions,
|
...options?.persistentContextOptions,
|
||||||
useWebSocket: isUnderTest(),
|
useWebSocket: isUnderTest(),
|
||||||
headless: !!options?.headless,
|
headless: !!options?.headless,
|
||||||
|
args: process.env.PWTEST_RECORDER_PORT ? [`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`] : [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
"@babel/code-frame": "^7.24.2",
|
"@babel/code-frame": "^7.24.2",
|
||||||
"@babel/core": "^7.24.4",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/helper-plugin-utils": "^7.24.0",
|
"@babel/helper-plugin-utils": "^7.24.0",
|
||||||
"@babel/parser": "^7.24.4",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||||
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
||||||
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
"@babel/code-frame": "^7.24.2",
|
"@babel/code-frame": "^7.24.2",
|
||||||
"@babel/core": "^7.24.4",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/helper-plugin-utils": "^7.24.0",
|
"@babel/helper-plugin-utils": "^7.24.0",
|
||||||
"@babel/parser": "^7.24.4",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||||
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
||||||
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import * as babel from '@babel/core';
|
||||||
export { codeFrameColumns } from '@babel/code-frame';
|
export { codeFrameColumns } from '@babel/code-frame';
|
||||||
export { declare } from '@babel/helper-plugin-utils';
|
export { declare } from '@babel/helper-plugin-utils';
|
||||||
export { types } from '@babel/core';
|
export { types } from '@babel/core';
|
||||||
export { parse } from '@babel/parser';
|
|
||||||
import traverseFunction from '@babel/traverse';
|
import traverseFunction from '@babel/traverse';
|
||||||
export const traverse = traverseFunction;
|
export const traverse = traverseFunction;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,11 @@ export class TestTypeImpl {
|
||||||
suite._use.push({ fixtures, location });
|
suite._use.push({ fixtures, location });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _step<T>(title: string, body: () => Promise<T>, options: { box?: boolean } = {}): Promise<T> {
|
async _step<T>(title: string, body: () => Promise<T>, options: {box?: boolean, location?: Location } = {}): Promise<T> {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`test.step() can only be called from a test`);
|
throw new Error(`test.step() can only be called from a test`);
|
||||||
const step = testInfo._addStep({ category: 'test.step', title, box: options.box });
|
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
||||||
return await zones.run('stepZone', step, async () => {
|
return await zones.run('stepZone', step, async () => {
|
||||||
try {
|
try {
|
||||||
const result = await body();
|
const result = await body();
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ class HtmlReporter implements ReporterV2 {
|
||||||
const shouldOpen = !this._options._isTestServer && (this._open === 'always' || (!ok && this._open === 'on-failure'));
|
const shouldOpen = !this._options._isTestServer && (this._open === 'always' || (!ok && this._open === 'on-failure'));
|
||||||
if (shouldOpen) {
|
if (shouldOpen) {
|
||||||
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
|
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
|
||||||
} else if (this._options._mode === 'test') {
|
} else if (this._options._mode === 'test' && !this._options._isTestServer) {
|
||||||
const packageManagerCommand = getPackageManagerExecCommand();
|
const packageManagerCommand = getPackageManagerExecCommand();
|
||||||
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder);
|
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder);
|
||||||
const hostArg = this._host ? ` --host ${this._host}` : '';
|
const hostArg = this._host ? ` --host ${this._host}` : '';
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import type { BabelFileResult } from '../../bundles/babel/node_modules/@types/ba
|
||||||
export const codeFrameColumns: typeof import('../../bundles/babel/node_modules/@types/babel__code-frame').codeFrameColumns = require('./babelBundleImpl').codeFrameColumns;
|
export const codeFrameColumns: typeof import('../../bundles/babel/node_modules/@types/babel__code-frame').codeFrameColumns = require('./babelBundleImpl').codeFrameColumns;
|
||||||
export const declare: typeof import('../../bundles/babel/node_modules/@types/babel__helper-plugin-utils').declare = require('./babelBundleImpl').declare;
|
export const declare: typeof import('../../bundles/babel/node_modules/@types/babel__helper-plugin-utils').declare = require('./babelBundleImpl').declare;
|
||||||
export const types: typeof import('../../bundles/babel/node_modules/@types/babel__core').types = require('./babelBundleImpl').types;
|
export const types: typeof import('../../bundles/babel/node_modules/@types/babel__core').types = require('./babelBundleImpl').types;
|
||||||
export const parse: typeof import('../../bundles/babel/node_modules/@babel/parser/typings/babel-parser').parse = require('./babelBundleImpl').parse;
|
|
||||||
export const traverse: typeof import('../../bundles/babel/node_modules/@types/babel__traverse').default = require('./babelBundleImpl').traverse;
|
export const traverse: typeof import('../../bundles/babel/node_modules/@types/babel__traverse').default = require('./babelBundleImpl').traverse;
|
||||||
export type BabelPlugin = [string, any?];
|
export type BabelPlugin = [string, any?];
|
||||||
export type BabelTransformFunction = (code: string, filename: string, isTypeScript: boolean, isModule: boolean, pluginsPrefix: BabelPlugin[], pluginsSuffix: BabelPlugin[]) => BabelFileResult;
|
export type BabelTransformFunction = (code: string, filename: string, isTypeScript: boolean, isModule: boolean, pluginsPrefix: BabelPlugin[], pluginsSuffix: BabelPlugin[]) => BabelFileResult;
|
||||||
|
|
|
||||||
2
packages/playwright/types/test.d.ts
vendored
|
|
@ -4703,7 +4703,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
||||||
* @param body Step body.
|
* @param body Step body.
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean }): Promise<T>;
|
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>;
|
||||||
/**
|
/**
|
||||||
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
|
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
285
packages/trace-viewer/src/third_party/devtools.ts
vendored
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||||
|
// Modifications copyright (c) Microsoft Corporation.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
|
||||||
|
* Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
|
||||||
|
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||||
|
* its contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Entry } from '@trace/har';
|
||||||
|
|
||||||
|
// The following function is derived from Chromium's source code
|
||||||
|
// https://github.com/ChromeDevTools/devtools-frontend/blob/83cbe41b4107e188a1f66fdf6ea3a9cca42587c6/front_end/panels/network/NetworkLogView.ts#L2363
|
||||||
|
export async function generateCurlCommand(resource: Entry): Promise<string> {
|
||||||
|
const platform = navigator.platform.includes('Win') ? 'win' : 'unix';
|
||||||
|
let command: string[] = [];
|
||||||
|
// Most of these headers are derived from the URL and are automatically added by cURL.
|
||||||
|
// The |Accept-Encoding| header is ignored to prevent decompression errors. crbug.com/1015321
|
||||||
|
const ignoredHeaders =
|
||||||
|
new Set<string>(['accept-encoding', 'host', 'method', 'path', 'scheme', 'version', 'authority', 'protocol']);
|
||||||
|
|
||||||
|
function escapeStringWin(str: string): string {
|
||||||
|
/* Always escape the " characters so that we can use caret escaping.
|
||||||
|
|
||||||
|
Because cmd.exe parser and MS Crt arguments parsers use some of the
|
||||||
|
same escape characters, they can interact with each other in
|
||||||
|
horrible ways, the order of operations is critical.
|
||||||
|
|
||||||
|
Replace \ with \\ first because it is an escape character for certain
|
||||||
|
conditions in both parsers.
|
||||||
|
|
||||||
|
Replace all " with \" to ensure the first parser does not remove it.
|
||||||
|
|
||||||
|
Then escape all characters we are not sure about with ^ to ensure it
|
||||||
|
gets to MS Crt parser safely.
|
||||||
|
|
||||||
|
The % character is special because MS Crt parser will try and look for
|
||||||
|
ENV variables and fill them in its place. We cannot escape them with %
|
||||||
|
and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
|
||||||
|
parser); So we can get cmd.exe parser to escape the character after it,
|
||||||
|
if it is followed by a valid beginning character of an ENV variable.
|
||||||
|
This ensures we do not try and double escape another ^ if it was placed
|
||||||
|
by the previous replace.
|
||||||
|
|
||||||
|
Lastly we replace new lines with ^ and TWO new lines because the first
|
||||||
|
new line is there to enact the escape command the second is the character
|
||||||
|
to escape (in this case new line).
|
||||||
|
*/
|
||||||
|
const encapsChars = '^"';
|
||||||
|
return encapsChars +
|
||||||
|
str.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g, '^$&')
|
||||||
|
.replace(/%(?=[a-zA-Z0-9_])/g, '%^')
|
||||||
|
.replace(/\r?\n/g, '^\n\n') +
|
||||||
|
encapsChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeStringPosix(str: string): string {
|
||||||
|
function escapeCharacter(x: string): string {
|
||||||
|
const code = x.charCodeAt(0);
|
||||||
|
let hexString = code.toString(16);
|
||||||
|
// Zero pad to four digits to comply with ANSI-C Quoting:
|
||||||
|
// http://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
|
||||||
|
while (hexString.length < 4)
|
||||||
|
hexString = '0' + hexString;
|
||||||
|
|
||||||
|
|
||||||
|
return '\\u' + hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/[\0-\x1F\x7F-\x9F!]|\'/.test(str)) {
|
||||||
|
// Use ANSI-C quoting syntax.
|
||||||
|
return '$\'' +
|
||||||
|
str.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/\'/g, '\\\'')
|
||||||
|
.replace(/\n/g, '\\n')
|
||||||
|
.replace(/\r/g, '\\r')
|
||||||
|
.replace(/[\0-\x1F\x7F-\x9F!]/g, escapeCharacter) +
|
||||||
|
'\'';
|
||||||
|
}
|
||||||
|
// Use single quote syntax.
|
||||||
|
return '\'' + str + '\'';
|
||||||
|
}
|
||||||
|
|
||||||
|
// cURL command expected to run on the same platform that DevTools run
|
||||||
|
// (it may be different from the inspected page platform).
|
||||||
|
const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix;
|
||||||
|
|
||||||
|
command.push(escapeString(resource.request.url).replace(/[[{}\]]/g, '\\$&'));
|
||||||
|
|
||||||
|
let inferredMethod = 'GET';
|
||||||
|
const data = [];
|
||||||
|
const formData = await fetchRequestPostData(resource);
|
||||||
|
if (formData) {
|
||||||
|
// Note that formData is not necessarily urlencoded because it might for example
|
||||||
|
// come from a fetch request made with an explicitly unencoded body.
|
||||||
|
data.push('--data-raw ' + escapeString(formData));
|
||||||
|
ignoredHeaders.add('content-length');
|
||||||
|
inferredMethod = 'POST';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.request.method !== inferredMethod)
|
||||||
|
command.push('-X ' + escapeString(resource.request.method));
|
||||||
|
|
||||||
|
|
||||||
|
const requestHeaders = resource.request.headers;
|
||||||
|
for (let i = 0; i < requestHeaders.length; i++) {
|
||||||
|
const header = requestHeaders[i];
|
||||||
|
const name = header.name.replace(/^:/, ''); // Translate SPDY v3 headers to HTTP headers.
|
||||||
|
if (ignoredHeaders.has(name.toLowerCase()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (header.value.trim()) {
|
||||||
|
command.push('-H ' + escapeString(name + ': ' + header.value));
|
||||||
|
} else {
|
||||||
|
// A header passed with -H with no value or only whitespace as its
|
||||||
|
// value tells curl to not set the header at all. To post an empty
|
||||||
|
// header, you have to terminate it with a semicolon.
|
||||||
|
command.push('-H ' + escapeString(name + ';'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command = command.concat(data);
|
||||||
|
|
||||||
|
return 'curl ' + command.join(command.length >= 3 ? (platform === 'win' ? ' ^\n ' : ' \\\n ') : ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum FetchStyle {
|
||||||
|
BROWSER = 0,
|
||||||
|
NODE_JS = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateFetchCall(resource: Entry, style: FetchStyle = FetchStyle.BROWSER): Promise<string> {
|
||||||
|
const ignoredHeaders = new Set<string>([
|
||||||
|
// Internal headers
|
||||||
|
'method',
|
||||||
|
'path',
|
||||||
|
'scheme',
|
||||||
|
'version',
|
||||||
|
|
||||||
|
// Unsafe headers
|
||||||
|
// Keep this list synchronized with src/net/http/http_util.cc
|
||||||
|
'accept-charset',
|
||||||
|
'accept-encoding',
|
||||||
|
'access-control-request-headers',
|
||||||
|
'access-control-request-method',
|
||||||
|
'connection',
|
||||||
|
'content-length',
|
||||||
|
'cookie',
|
||||||
|
'cookie2',
|
||||||
|
'date',
|
||||||
|
'dnt',
|
||||||
|
'expect',
|
||||||
|
'host',
|
||||||
|
'keep-alive',
|
||||||
|
'origin',
|
||||||
|
'referer',
|
||||||
|
'te',
|
||||||
|
'trailer',
|
||||||
|
'transfer-encoding',
|
||||||
|
'upgrade',
|
||||||
|
'via',
|
||||||
|
// TODO(phistuck) - remove this once crbug.com/571722 is fixed.
|
||||||
|
'user-agent',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const credentialHeaders = new Set<string>(['cookie', 'authorization']);
|
||||||
|
|
||||||
|
const url = JSON.stringify(resource.request.url);
|
||||||
|
|
||||||
|
const requestHeaders = resource.request.headers;
|
||||||
|
const headerData: Headers = requestHeaders.reduce((result, header) => {
|
||||||
|
const name = header.name;
|
||||||
|
|
||||||
|
if (!ignoredHeaders.has(name.toLowerCase()) && !name.includes(':'))
|
||||||
|
result.append(name, header.value);
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, new Headers());
|
||||||
|
|
||||||
|
const headers: HeadersInit = {};
|
||||||
|
for (const headerArray of headerData)
|
||||||
|
headers[headerArray[0]] = headerArray[1];
|
||||||
|
|
||||||
|
|
||||||
|
const credentials = resource.request.cookies.length ||
|
||||||
|
requestHeaders.some(({ name }) => credentialHeaders.has(name.toLowerCase())) ?
|
||||||
|
'include' :
|
||||||
|
'omit';
|
||||||
|
|
||||||
|
const referrerHeader = requestHeaders.find(({ name }) => name.toLowerCase() === 'referer');
|
||||||
|
|
||||||
|
const referrer = referrerHeader ? referrerHeader.value : void 0;
|
||||||
|
|
||||||
|
const requestBody = await fetchRequestPostData(resource);
|
||||||
|
|
||||||
|
const fetchOptions: RequestInit = {
|
||||||
|
headers: Object.keys(headers).length ? headers : void 0,
|
||||||
|
referrer,
|
||||||
|
body: requestBody,
|
||||||
|
method: resource.request.method,
|
||||||
|
mode: 'cors',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (style === FetchStyle.NODE_JS) {
|
||||||
|
const cookieHeader = requestHeaders.find(header => header.name.toLowerCase() === 'cookie');
|
||||||
|
const extraHeaders: HeadersInit = {};
|
||||||
|
// According to https://www.npmjs.com/package/node-fetch#class-request the
|
||||||
|
// following properties are not implemented in Node.js.
|
||||||
|
delete fetchOptions.mode;
|
||||||
|
if (cookieHeader)
|
||||||
|
extraHeaders['cookie'] = cookieHeader.value;
|
||||||
|
|
||||||
|
if (referrer) {
|
||||||
|
delete fetchOptions.referrer;
|
||||||
|
extraHeaders['Referer'] = referrer;
|
||||||
|
}
|
||||||
|
if (Object.keys(extraHeaders).length) {
|
||||||
|
fetchOptions.headers = {
|
||||||
|
...headers,
|
||||||
|
...extraHeaders,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fetchOptions.credentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = JSON.stringify(fetchOptions, null, 2);
|
||||||
|
return `fetch(${url}, ${options});`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRequestPostData(resource: Entry) {
|
||||||
|
return resource.request.postData?._sha1 ? await fetch(`sha1/${resource.request.postData._sha1}`).then(r => r.text()) : resource.request.postData?.text;
|
||||||
|
}
|
||||||
|
|
@ -73,6 +73,7 @@ export class TraceModel {
|
||||||
unzipProgress(++done, total);
|
unzipProgress(++done, total);
|
||||||
|
|
||||||
contextEntry.actions = modernizer.actions().sort((a1, a2) => a1.startTime - a2.startTime);
|
contextEntry.actions = modernizer.actions().sort((a1, a2) => a1.startTime - a2.startTime);
|
||||||
|
|
||||||
if (!backend.isLive()) {
|
if (!backend.isLive()) {
|
||||||
// Terminate actions w/o after event gracefully.
|
// Terminate actions w/o after event gracefully.
|
||||||
// This would close after hooks event that has not been closed because
|
// This would close after hooks event that has not been closed because
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@
|
||||||
../entries.ts
|
../entries.ts
|
||||||
../geometry.ts
|
../geometry.ts
|
||||||
../../../playwright/src/isomorphic/**
|
../../../playwright/src/isomorphic/**
|
||||||
|
../third_party/devtools.ts
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,22 @@ import * as React from 'react';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
|
|
||||||
export const CopyToClipboard: React.FunctionComponent<{
|
export const CopyToClipboard: React.FunctionComponent<{
|
||||||
value: string,
|
value: string | (() => Promise<string>),
|
||||||
description?: string,
|
description?: string,
|
||||||
}> = ({ value, description }) => {
|
}> = ({ value, description }) => {
|
||||||
const [icon, setIcon] = React.useState('copy');
|
const [icon, setIcon] = React.useState('copy');
|
||||||
|
|
||||||
const handleCopy = React.useCallback(() => {
|
const handleCopy = React.useCallback(() => {
|
||||||
navigator.clipboard.writeText(value).then(() => {
|
const valuePromise = typeof value === 'function' ? value() : Promise.resolve(value);
|
||||||
setIcon('check');
|
valuePromise.then(value => {
|
||||||
setTimeout(() => {
|
navigator.clipboard.writeText(value).then(() => {
|
||||||
setIcon('copy');
|
setIcon('check');
|
||||||
}, 3000);
|
setTimeout(() => {
|
||||||
}, () => {
|
setIcon('copy');
|
||||||
setIcon('close');
|
}, 3000);
|
||||||
|
}, () => {
|
||||||
|
setIcon('close');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts: ContextEn
|
||||||
for (const action of context.actions) {
|
for (const action of context.actions) {
|
||||||
if (!action.startTime)
|
if (!action.startTime)
|
||||||
continue;
|
continue;
|
||||||
const key = matchByStepId ? action.stepId! : `${action.apiName}@${(action as any).wallTime}`;
|
const key = matchByStepId ? action.callId! : `${action.apiName}@${(action as any).wallTime}`;
|
||||||
const libraryAction = libraryActions.get(key);
|
const libraryAction = libraryActions.get(key);
|
||||||
if (libraryAction)
|
if (libraryAction)
|
||||||
return action.startTime - libraryAction.startTime;
|
return action.startTime - libraryAction.startTime;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,15 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.network-request-details-copy {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-request-details-copy button {
|
||||||
|
border-radius: 4px
|
||||||
|
}
|
||||||
|
|
||||||
.network-font-preview {
|
.network-font-preview {
|
||||||
font-family: font-preview;
|
font-family: font-preview;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import './networkResourceDetails.css';
|
||||||
import { TabbedPane } from '@web/components/tabbedPane';
|
import { TabbedPane } from '@web/components/tabbedPane';
|
||||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||||
|
import { generateCurlCommand, generateFetchCall } from '../third_party/devtools';
|
||||||
|
import { CopyToClipboard } from './copyToClipboard';
|
||||||
|
|
||||||
export const NetworkResourceDetails: React.FunctionComponent<{
|
export const NetworkResourceDetails: React.FunctionComponent<{
|
||||||
resource: ResourceSnapshot;
|
resource: ResourceSnapshot;
|
||||||
|
|
@ -90,6 +92,13 @@ const RequestTab: React.FunctionComponent<{
|
||||||
</> : null}
|
</> : null}
|
||||||
<div className='network-request-details-header'>Request Headers</div>
|
<div className='network-request-details-header'>Request Headers</div>
|
||||||
<div className='network-request-details-headers'>{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}</div>
|
<div className='network-request-details-headers'>{resource.request.headers.map(pair => `${pair.name}: ${pair.value}`).join('\n')}</div>
|
||||||
|
<div className='network-request-details-header'>Copy Request</div>
|
||||||
|
<div className='network-request-details-copy'>
|
||||||
|
As cURL: <CopyToClipboard description='Copy as cURL' value={() => generateCurlCommand(resource)}/>
|
||||||
|
</div>
|
||||||
|
<div className='network-request-details-copy'>
|
||||||
|
As Fetch: <CopyToClipboard description='Copy as Fetch' value={() => generateFetchCall(resource)}/>
|
||||||
|
</div>
|
||||||
{requestBody && <div className='network-request-details-header'>Request Body</div>}
|
{requestBody && <div className='network-request-details-header'>Request Body</div>}
|
||||||
{requestBody && <CodeMirrorWrapper text={requestBody.text} mimeType={requestBody.mimeType} readOnly lineNumbers={true}/>}
|
{requestBody && <CodeMirrorWrapper text={requestBody.text} mimeType={requestBody.mimeType} readOnly lineNumbers={true}/>}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ export const RecorderView: React.FunctionComponent = () => {
|
||||||
connection.setMode('recording');
|
connection.setMode('recording');
|
||||||
}, [connection]);
|
}, [connection]);
|
||||||
|
|
||||||
|
window.playwrightSourcesEchoForTest = sources;
|
||||||
|
|
||||||
return <div className='vbox workbench-loader'>
|
return <div className='vbox workbench-loader'>
|
||||||
<TraceView
|
<TraceView
|
||||||
traceLocation={trace}
|
traceLocation={trace}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import './toolbarButton.css';
|
import './toolbarButton.css';
|
||||||
import '../third_party/vscode/codicon.css';
|
import '../third_party/vscode/codicon.css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { clsx } from '@web/uiUtils';
|
||||||
|
|
||||||
export interface ToolbarButtonProps {
|
export interface ToolbarButtonProps {
|
||||||
title: string,
|
title: string,
|
||||||
|
|
@ -40,11 +41,8 @@ export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>
|
||||||
testId,
|
testId,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
className = (className || '') + ` toolbar-button ${icon}`;
|
|
||||||
if (toggled)
|
|
||||||
className += ' toggled';
|
|
||||||
return <button
|
return <button
|
||||||
className={className}
|
className={clsx(className, 'toolbar-button', icon, toggled && 'toggled')}
|
||||||
onMouseDown={preventDefault}
|
onMouseDown={preventDefault}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onDoubleClick={preventDefault}
|
onDoubleClick={preventDefault}
|
||||||
|
|
|
||||||
BIN
tests/assets/trace-remote-time-diff.zip
Normal file
|
|
@ -69,12 +69,16 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
|
||||||
await run(false);
|
await run(false);
|
||||||
}, { scope: 'worker' }],
|
}, { scope: 'worker' }],
|
||||||
|
|
||||||
defaultSameSiteCookieValue: [async ({ browserName, isLinux }, run) => {
|
defaultSameSiteCookieValue: [async ({ browserName, platform }, run) => {
|
||||||
if (browserName === 'chromium' || browserName as any === '_bidiChromium')
|
if (browserName === 'chromium' || browserName as any === '_bidiChromium')
|
||||||
await run('Lax');
|
await run('Lax');
|
||||||
else if (browserName === 'webkit' && isLinux)
|
else if (browserName === 'webkit' && platform === 'linux')
|
||||||
await run('Lax');
|
await run('Lax');
|
||||||
else if (browserName === 'webkit' && !isLinux)
|
else if (browserName === 'webkit' && platform === 'darwin' && parseInt(os.release(), 10) >= 24)
|
||||||
|
// macOS 15 Sequoia onwards
|
||||||
|
await run('Lax');
|
||||||
|
else if (browserName === 'webkit')
|
||||||
|
// Windows + older macOS
|
||||||
await run('None');
|
await run('None');
|
||||||
else if (browserName === 'firefox' || browserName as any === '_bidiFirefox')
|
else if (browserName === 'firefox' || browserName as any === '_bidiFirefox')
|
||||||
await run('None');
|
await run('None');
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import os from 'os';
|
||||||
import { contextTest as it, expect } from '../config/browserTest';
|
import { contextTest as it, expect } from '../config/browserTest';
|
||||||
|
|
||||||
it('should return no cookies in pristine browser context', async ({ context, page, server }) => {
|
it('should return no cookies in pristine browser context', async ({ context, page, server }) => {
|
||||||
|
|
@ -396,7 +397,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse
|
||||||
server.waitForRequest('/title.html'),
|
server.waitForRequest('/title.html'),
|
||||||
frame.evaluate(() => fetch('/title.html'))
|
frame.evaluate(() => fetch('/title.html'))
|
||||||
]);
|
]);
|
||||||
if (isLinux && browserName === 'webkit')
|
if ((isLinux || (isMac && parseInt(os.release(), 10) >= 24)) && browserName === 'webkit')
|
||||||
expect(serverRequest.headers.cookie).toBe(undefined);
|
expect(serverRequest.headers.cookie).toBe(undefined);
|
||||||
else
|
else
|
||||||
expect(serverRequest.headers.cookie).toBe('name=value');
|
expect(serverRequest.headers.cookie).toBe('name=value');
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,7 @@ it('should have security details', async ({ contextFactory, httpsServer, browser
|
||||||
|
|
||||||
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
|
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
|
||||||
await page.goto(httpsServer.EMPTY_PAGE);
|
await page.goto(httpsServer.EMPTY_PAGE);
|
||||||
|
await page.request.get(httpsServer.EMPTY_PAGE);
|
||||||
const log = await getLog();
|
const log = await getLog();
|
||||||
const { serverIPAddress, _serverPort: port, _securityDetails: securityDetails } = log.entries[0];
|
const { serverIPAddress, _serverPort: port, _securityDetails: securityDetails } = log.entries[0];
|
||||||
if (!mode.startsWith('service')) {
|
if (!mode.startsWith('service')) {
|
||||||
|
|
@ -620,6 +621,8 @@ it('should have security details', async ({ contextFactory, httpsServer, browser
|
||||||
expect(securityDetails).toEqual({ protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 });
|
expect(securityDetails).toEqual({ protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 });
|
||||||
else
|
else
|
||||||
expect(securityDetails).toEqual({ issuer: 'playwright-test', protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 });
|
expect(securityDetails).toEqual({ issuer: 'playwright-test', protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 });
|
||||||
|
|
||||||
|
expect(log.entries[1]._securityDetails).toEqual({ issuer: 'playwright-test', protocol: 'TLSv1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have connection details for redirects', async ({ contextFactory, server, browserName, mode }, testInfo) => {
|
it('should have connection details for redirects', async ({ contextFactory, server, browserName, mode }, testInfo) => {
|
||||||
|
|
@ -820,6 +823,7 @@ it('should include API request', async ({ contextFactory, server }, testInfo) =>
|
||||||
expect(entry.response.headers.find(h => h.name.toLowerCase() === 'content-type')?.value).toContain('application/json');
|
expect(entry.response.headers.find(h => h.name.toLowerCase() === 'content-type')?.value).toContain('application/json');
|
||||||
expect(entry.response.content.size).toBe(15);
|
expect(entry.response.content.size).toBe(15);
|
||||||
expect(entry.response.content.text).toBe(responseBody.toString());
|
expect(entry.response.content.text).toBe(responseBody.toString());
|
||||||
|
expect(entry.response.bodySize).toBe(15);
|
||||||
|
|
||||||
expect(entry.time).toBeGreaterThan(0);
|
expect(entry.time).toBeGreaterThan(0);
|
||||||
expect(entry.timings).toEqual(expect.objectContaining({
|
expect(entry.timings).toEqual(expect.objectContaining({
|
||||||
|
|
@ -831,6 +835,9 @@ it('should include API request', async ({ contextFactory, server }, testInfo) =>
|
||||||
ssl: expect.any(Number),
|
ssl: expect.any(Number),
|
||||||
wait: expect.any(Number),
|
wait: expect.any(Number),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
expect(entry.serverIPAddress).toBeDefined();
|
||||||
|
expect(entry._serverPort).toEqual(server.PORT);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respect minimal mode for API Requests', async ({ contextFactory, server }, testInfo) => {
|
it('should respect minimal mode for API Requests', async ({ contextFactory, server }, testInfo) => {
|
||||||
|
|
@ -844,6 +851,11 @@ it('should respect minimal mode for API Requests', async ({ contextFactory, serv
|
||||||
expect(entries).toHaveLength(1);
|
expect(entries).toHaveLength(1);
|
||||||
const [entry] = entries;
|
const [entry] = entries;
|
||||||
expect(entry.timings).toEqual({ receive: -1, send: -1, wait: -1 });
|
expect(entry.timings).toEqual({ receive: -1, send: -1, wait: -1 });
|
||||||
|
expect(entry.serverIPAddress).toBeUndefined();
|
||||||
|
expect(entry._serverPort).toBeUndefined();
|
||||||
|
expect(entry.request.cookies).toEqual([]);
|
||||||
|
expect(entry.request.bodySize).toBe(-1);
|
||||||
|
expect(entry.response.bodySize).toBe(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include redirects from API request', async ({ contextFactory, server }, testInfo) => {
|
it('should include redirects from API request', async ({ contextFactory, server }, testInfo) => {
|
||||||
|
|
|
||||||
|
|
@ -744,7 +744,7 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
|
||||||
const recorder = await openRecorder();
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
const hydrate = () => {
|
const hydrate = () => {
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
document.documentElement.innerHTML = '<p>Post-Hydration Content</p>';
|
document.documentElement.innerHTML = '<p>Post-Hydration Content</p>';
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export const test = contextTest.extend<CLITestArgs>({
|
||||||
await run(async () => {
|
await run(async () => {
|
||||||
while (!toImpl(context).recorderAppForTest)
|
while (!toImpl(context).recorderAppForTest)
|
||||||
await new Promise(f => setTimeout(f, 100));
|
await new Promise(f => setTimeout(f, 100));
|
||||||
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpoint;
|
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpointForTest;
|
||||||
const browser = await playwrightToAutomateInspector.chromium.connectOverCDP({ wsEndpoint });
|
const browser = await playwrightToAutomateInspector.chromium.connectOverCDP({ wsEndpoint });
|
||||||
const c = browser.contexts()[0];
|
const c = browser.contexts()[0];
|
||||||
return c.pages()[0] || await c.waitForEvent('page');
|
return c.pages()[0] || await c.waitForEvent('page');
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,12 @@ test('should complain about newer version of trace in old viewer', async ({ show
|
||||||
await expect(traceViewer.page.getByText('The trace was created by a newer version of Playwright and is not supported by this version of the viewer.')).toBeVisible();
|
await expect(traceViewer.page.getByText('The trace was created by a newer version of Playwright and is not supported by this version of the viewer.')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should properly synchronize local and remote time', async ({ showTraceViewer, asset }, testInfo) => {
|
||||||
|
const traceViewer = await showTraceViewer([asset('trace-remote-time-diff.zip')]);
|
||||||
|
// The total duration should be sub 10s, rather than 16h.
|
||||||
|
await expect(traceViewer.page.locator('.timeline-time').last()).toHaveText('8.5s');
|
||||||
|
});
|
||||||
|
|
||||||
test('should contain action info', async ({ showTraceViewer }) => {
|
test('should contain action info', async ({ showTraceViewer }) => {
|
||||||
const traceViewer = await showTraceViewer([traceFile]);
|
const traceViewer = await showTraceViewer([traceFile]);
|
||||||
await traceViewer.selectAction('locator.click');
|
await traceViewer.selectAction('locator.click');
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 474 B |
|
After Width: | Height: | Size: 138 B |
|
After Width: | Height: | Size: 143 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 301 B |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 94 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -1246,3 +1246,42 @@ fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('test location to test.step', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'reporter.ts': stepIndentReporter,
|
||||||
|
'helper.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
|
export async function dummyStep(test, title, action, location) {
|
||||||
|
return await test.step(title, action, { location });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCustomLocation() {
|
||||||
|
return { file: 'dummy-file.ts', line: 123, column: 45 };
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
reporter: './reporter',
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { dummyStep, getCustomLocation } from './helper';
|
||||||
|
|
||||||
|
test('custom location test', async () => {
|
||||||
|
const location = getCustomLocation();
|
||||||
|
await dummyStep(test, 'Perform a dummy step', async () => {
|
||||||
|
}, location);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { reporter: '', workers: 1 });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
|
hook |Before Hooks
|
||||||
|
test.step |Perform a dummy step @ dummy-file.ts:123
|
||||||
|
hook |After Hooks
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
@ -215,6 +215,24 @@ test('should run tests on Enter', async ({ runWatchTest }) => {
|
||||||
await testProcess.waitForOutput('Waiting for file changes.');
|
await testProcess.waitForOutput('Waiting for file changes.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not print show-report command of HTML reporter', async ({ runWatchTest }) => {
|
||||||
|
const testProcess = await runWatchTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
export default defineConfig({ reporter: 'html' });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
await testProcess.waitForOutput('Waiting for file changes.');
|
||||||
|
testProcess.clearOutput();
|
||||||
|
testProcess.write('\r\n');
|
||||||
|
await testProcess.waitForOutput('Waiting for file changes.');
|
||||||
|
expect(testProcess.output).not.toContain('To open last HTML report run:');
|
||||||
|
});
|
||||||
|
|
||||||
test('should run tests on R', async ({ runWatchTest }) => {
|
test('should run tests on R', async ({ runWatchTest }) => {
|
||||||
const testProcess = await runWatchTest({
|
const testProcess = await runWatchTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
|
|
||||||
2
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -128,7 +128,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
||||||
afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
||||||
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
||||||
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
||||||
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean }): Promise<T>;
|
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>;
|
||||||
expect: Expect<{}>;
|
expect: Expect<{}>;
|
||||||
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
info(): TestInfo;
|
info(): TestInfo;
|
||||||
|
|
|
||||||