mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: watch mode first cut (#20647)
This commit is contained in:
parent
b6df48758d
commit
430d08f4fb
45
package-lock.json
generated
45
package-lock.json
generated
@ -2037,7 +2037,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
@ -2169,14 +2168,14 @@
|
|||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
"dev": true,
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
|
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
@ -2195,7 +2194,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/chokidar/node_modules/anymatch": {
|
"node_modules/chokidar/node_modules/anymatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@ -2207,7 +2205,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/chokidar/node_modules/binary-extensions": {
|
"node_modules/chokidar/node_modules/binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@ -2215,7 +2212,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/chokidar/node_modules/is-binary-path": {
|
"node_modules/chokidar/node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
@ -3521,7 +3517,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@ -3676,7 +3671,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@ -3943,7 +3937,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -3959,7 +3952,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/is-glob": {
|
"node_modules/is-glob": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@ -3970,7 +3962,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
@ -4419,7 +4410,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -4602,7 +4592,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@ -4876,7 +4865,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@ -5397,7 +5385,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
@ -6091,6 +6078,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
"chokidar": "3.5.3",
|
||||||
"playwright-core": "1.31.0-next"
|
"playwright-core": "1.31.0-next"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -6935,6 +6923,7 @@
|
|||||||
"version": "file:packages/playwright-test",
|
"version": "file:packages/playwright-test",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
"chokidar": "3.5.3",
|
||||||
"playwright-core": "1.31.0-next"
|
"playwright-core": "1.31.0-next"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -7427,7 +7416,6 @@
|
|||||||
},
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
}
|
}
|
||||||
@ -7503,7 +7491,8 @@
|
|||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
"dev": true,
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
|
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
@ -7517,19 +7506,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"picomatch": "^2.0.4"
|
"picomatch": "^2.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"binary-extensions": {
|
"binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-binary-path": {
|
"is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -8328,7 +8314,6 @@
|
|||||||
},
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
}
|
}
|
||||||
@ -8430,7 +8415,6 @@
|
|||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
}
|
}
|
||||||
@ -8609,8 +8593,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-extglob": {
|
"is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@ -8618,14 +8601,12 @@
|
|||||||
},
|
},
|
||||||
"is-glob": {
|
"is-glob": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-what": {
|
"is-what": {
|
||||||
"version": "4.1.8",
|
"version": "4.1.8",
|
||||||
@ -8916,8 +8897,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"normalize-path": {
|
"normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"normalize-url": {
|
"normalize-url": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
@ -9033,8 +9013,7 @@
|
|||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@ -9220,7 +9199,6 @@
|
|||||||
},
|
},
|
||||||
"readdirp": {
|
"readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
}
|
}
|
||||||
@ -9548,7 +9526,6 @@
|
|||||||
},
|
},
|
||||||
"to-regex-range": {
|
"to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ This project incorporates components from the projects listed below. The origina
|
|||||||
- @types/stack-utils@2.0.1 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
- @types/stack-utils@2.0.1 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||||
- @types/yargs-parser@21.0.0 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
- @types/yargs-parser@21.0.0 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||||
- @types/yargs@16.0.4 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
- @types/yargs@16.0.4 (https://github.com/DefinitelyTyped/DefinitelyTyped)
|
||||||
|
- ansi-colors@4.1.3 (https://github.com/doowb/ansi-colors)
|
||||||
- ansi-regex@5.0.1 (https://github.com/chalk/ansi-regex)
|
- ansi-regex@5.0.1 (https://github.com/chalk/ansi-regex)
|
||||||
- ansi-styles@3.2.1 (https://github.com/chalk/ansi-styles)
|
- ansi-styles@3.2.1 (https://github.com/chalk/ansi-styles)
|
||||||
- ansi-styles@4.3.0 (https://github.com/chalk/ansi-styles)
|
- ansi-styles@4.3.0 (https://github.com/chalk/ansi-styles)
|
||||||
@ -94,6 +95,7 @@ This project incorporates components from the projects listed below. The origina
|
|||||||
- define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop)
|
- define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop)
|
||||||
- diff-sequences@27.5.1 (https://github.com/facebook/jest)
|
- diff-sequences@27.5.1 (https://github.com/facebook/jest)
|
||||||
- electron-to-chromium@1.4.284 (https://github.com/kilian/electron-to-chromium)
|
- electron-to-chromium@1.4.284 (https://github.com/kilian/electron-to-chromium)
|
||||||
|
- enquirer@2.3.6 (https://github.com/enquirer/enquirer)
|
||||||
- escalade@3.1.1 (https://github.com/lukeed/escalade)
|
- escalade@3.1.1 (https://github.com/lukeed/escalade)
|
||||||
- escape-string-regexp@1.0.5 (https://github.com/sindresorhus/escape-string-regexp)
|
- escape-string-regexp@1.0.5 (https://github.com/sindresorhus/escape-string-regexp)
|
||||||
- escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp)
|
- escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp)
|
||||||
@ -2179,6 +2181,32 @@ MIT License
|
|||||||
=========================================
|
=========================================
|
||||||
END OF @types/yargs@16.0.4 AND INFORMATION
|
END OF @types/yargs@16.0.4 AND INFORMATION
|
||||||
|
|
||||||
|
%% ansi-colors@4.1.3 NOTICES AND INFORMATION BEGIN HERE
|
||||||
|
=========================================
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present, Brian Woodward.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
=========================================
|
||||||
|
END OF ansi-colors@4.1.3 AND INFORMATION
|
||||||
|
|
||||||
%% ansi-regex@5.0.1 NOTICES AND INFORMATION BEGIN HERE
|
%% ansi-regex@5.0.1 NOTICES AND INFORMATION BEGIN HERE
|
||||||
=========================================
|
=========================================
|
||||||
MIT License
|
MIT License
|
||||||
@ -2944,6 +2972,32 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
|
|||||||
=========================================
|
=========================================
|
||||||
END OF electron-to-chromium@1.4.284 AND INFORMATION
|
END OF electron-to-chromium@1.4.284 AND INFORMATION
|
||||||
|
|
||||||
|
%% enquirer@2.3.6 NOTICES AND INFORMATION BEGIN HERE
|
||||||
|
=========================================
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-present, Jon Schlinkert.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
=========================================
|
||||||
|
END OF enquirer@2.3.6 AND INFORMATION
|
||||||
|
|
||||||
%% escalade@3.1.1 NOTICES AND INFORMATION BEGIN HERE
|
%% escalade@3.1.1 NOTICES AND INFORMATION BEGIN HERE
|
||||||
=========================================
|
=========================================
|
||||||
MIT License
|
MIT License
|
||||||
@ -3869,6 +3923,6 @@ END OF update-browserslist-db@1.0.10 AND INFORMATION
|
|||||||
|
|
||||||
SUMMARY BEGIN HERE
|
SUMMARY BEGIN HERE
|
||||||
=========================================
|
=========================================
|
||||||
Total Packages: 132
|
Total Packages: 134
|
||||||
=========================================
|
=========================================
|
||||||
END OF SUMMARY
|
END OF SUMMARY
|
@ -8,6 +8,7 @@
|
|||||||
"name": "utils-bundle",
|
"name": "utils-bundle",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"enquirer": "^2.3.6",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"pirates": "4.0.4",
|
"pirates": "4.0.4",
|
||||||
@ -43,6 +44,14 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ansi-colors": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@ -56,6 +65,17 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/enquirer": {
|
||||||
|
"version": "2.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
|
||||||
|
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-colors": "^4.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-docker": {
|
"node_modules/is-docker": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||||
@ -168,6 +188,11 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ansi-colors": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="
|
||||||
|
},
|
||||||
"buffer-from": {
|
"buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@ -178,6 +203,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
||||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="
|
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="
|
||||||
},
|
},
|
||||||
|
"enquirer": {
|
||||||
|
"version": "2.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
|
||||||
|
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-colors": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"is-docker": {
|
"is-docker": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
|
"generate-license": "node ../../../../utils/generate_third_party_notice.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"enquirer": "^2.3.6",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"pirates": "4.0.4",
|
"pirates": "4.0.4",
|
||||||
|
@ -28,3 +28,6 @@ export const sourceMapSupport = sourceMapSupportLibrary;
|
|||||||
|
|
||||||
import stoppableLibrary from 'stoppable';
|
import stoppableLibrary from 'stoppable';
|
||||||
export const stoppable = stoppableLibrary;
|
export const stoppable = stoppableLibrary;
|
||||||
|
|
||||||
|
import enquirerLibrary from 'enquirer';
|
||||||
|
export const enquirer = enquirerLibrary;
|
@ -34,6 +34,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
"chokidar": "3.5.3",
|
||||||
"playwright-core": "1.31.0-next"
|
"playwright-core": "1.31.0-next"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Runner } from './runner/runner';
|
import { Runner } from './runner/runner';
|
||||||
import { stopProfiling, startProfiling } from './common/profiler';
|
import { stopProfiling, startProfiling } from './common/profiler';
|
||||||
import { experimentalLoaderOption, fileIsModule } from './util';
|
import { createFileFilterForArg, experimentalLoaderOption, fileIsModule, forceRegExp } from './util';
|
||||||
import { createTitleMatcher } from './util';
|
import { createTitleMatcher } from './util';
|
||||||
import { showHTMLReport } from './reporters/html';
|
import { showHTMLReport } from './reporters/html';
|
||||||
import { baseFullConfig, builtInReporters, ConfigLoader, defaultTimeout, kDefaultConfigFiles, resolveConfigFile } from './common/configLoader';
|
import { baseFullConfig, builtInReporters, ConfigLoader, defaultTimeout, kDefaultConfigFiles, resolveConfigFile } from './common/configLoader';
|
||||||
@ -61,6 +61,7 @@ function addTestCommand(program: Command) {
|
|||||||
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`);
|
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`);
|
||||||
command.option('--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`);
|
command.option('--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`);
|
||||||
command.option('--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`);
|
command.option('--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`);
|
||||||
|
command.option('--watch', `Run watch mode`);
|
||||||
command.option('-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`);
|
command.option('-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`);
|
||||||
command.option('-x', `Stop after the first failure`);
|
command.option('-x', `Stop after the first failure`);
|
||||||
command.action(async (args, opts) => {
|
command.action(async (args, opts) => {
|
||||||
@ -159,14 +160,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||||||
configLoader.ignoreProjectDependencies();
|
configLoader.ignoreProjectDependencies();
|
||||||
|
|
||||||
const config = configLoader.fullConfig();
|
const config = configLoader.fullConfig();
|
||||||
config._internal.cliFileFilters = args.map(arg => {
|
config._internal.cliFileFilters = args.map(arg => createFileFilterForArg(arg));
|
||||||
const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
|
|
||||||
return {
|
|
||||||
re: forceRegExp(match ? match[1] : arg),
|
|
||||||
line: match ? parseInt(match[2], 10) : null,
|
|
||||||
column: match?.[3] ? parseInt(match[3], 10) : null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const grepMatcher = opts.grep ? createTitleMatcher(forceRegExp(opts.grep)) : () => true;
|
const grepMatcher = opts.grep ? createTitleMatcher(forceRegExp(opts.grep)) : () => true;
|
||||||
const grepInvertMatcher = opts.grepInvert ? createTitleMatcher(forceRegExp(opts.grepInvert)) : () => false;
|
const grepInvertMatcher = opts.grepInvert ? createTitleMatcher(forceRegExp(opts.grepInvert)) : () => false;
|
||||||
config._internal.cliTitleMatcher = (title: string) => !grepInvertMatcher(title) && grepMatcher(title);
|
config._internal.cliTitleMatcher = (title: string) => !grepInvertMatcher(title) && grepMatcher(title);
|
||||||
@ -175,7 +169,9 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||||||
config._internal.passWithNoTests = !!opts.passWithNoTests;
|
config._internal.passWithNoTests = !!opts.passWithNoTests;
|
||||||
|
|
||||||
const runner = new Runner(config);
|
const runner = new Runner(config);
|
||||||
const status = await runner.runAllTests();
|
if (opts.watch)
|
||||||
|
process.stdout.write('\x1Bc');
|
||||||
|
const status = await runner.runAllTests(!!opts.watch);
|
||||||
await stopProfiling(undefined);
|
await stopProfiling(undefined);
|
||||||
|
|
||||||
if (status === 'interrupted')
|
if (status === 'interrupted')
|
||||||
@ -201,13 +197,6 @@ async function listTestFiles(opts: { [key: string]: any }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function forceRegExp(pattern: string): RegExp {
|
|
||||||
const match = pattern.match(/^\/(.*)\/([gi]*)$/);
|
|
||||||
if (match)
|
|
||||||
return new RegExp(match[1], match[2]);
|
|
||||||
return new RegExp(pattern, 'gi');
|
|
||||||
}
|
|
||||||
|
|
||||||
function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrides {
|
function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrides {
|
||||||
const shardPair = options.shard ? options.shard.split('/').map((t: string) => parseInt(t, 10)) : undefined;
|
const shardPair = options.shard ? options.shard.split('/').map((t: string) => parseInt(t, 10)) : undefined;
|
||||||
return {
|
return {
|
||||||
|
@ -18,7 +18,7 @@ import path from 'path';
|
|||||||
import { calculateSha1 } from 'playwright-core/lib/utils';
|
import { calculateSha1 } from 'playwright-core/lib/utils';
|
||||||
import type { Suite, TestCase } from './test';
|
import type { Suite, TestCase } from './test';
|
||||||
import type { FullProjectInternal } from './types';
|
import type { FullProjectInternal } from './types';
|
||||||
import type { TestFileFilter } from '../util';
|
import type { Matcher, TestFileFilter } from '../util';
|
||||||
import { createFileMatcher } from '../util';
|
import { createFileMatcher } from '../util';
|
||||||
|
|
||||||
|
|
||||||
@ -114,6 +114,12 @@ export function filterByFocusedLine(suite: Suite, focusedTestFileLines: TestFile
|
|||||||
return filterSuite(suite, suiteFilter, testFilter);
|
return filterSuite(suite, suiteFilter, testFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterByTestIds(suite: Suite, testIdMatcher: Matcher | undefined) {
|
||||||
|
if (!testIdMatcher)
|
||||||
|
return;
|
||||||
|
filterTestsRemoveEmptySuites(suite, test => testIdMatcher(test.id));
|
||||||
|
}
|
||||||
|
|
||||||
function createFileMatcherFromFilter(filter: TestFileFilter) {
|
function createFileMatcherFromFilter(filter: TestFileFilter) {
|
||||||
const fileMatcher = createFileMatcher(filter.re || filter.exact || '');
|
const fileMatcher = createFileMatcher(filter.re || filter.exact || '');
|
||||||
return (testFileName: string, testLine: number, testColumn: number) =>
|
return (testFileName: string, testLine: number, testColumn: number) =>
|
||||||
|
@ -53,6 +53,7 @@ type ConfigInternal = {
|
|||||||
cliFileFilters: TestFileFilter[];
|
cliFileFilters: TestFileFilter[];
|
||||||
cliTitleMatcher: Matcher;
|
cliTitleMatcher: Matcher;
|
||||||
cliProjectFilter?: string[];
|
cliProjectFilter?: string[];
|
||||||
|
testIdMatcher?: Matcher;
|
||||||
passWithNoTests?: boolean;
|
passWithNoTests?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ export interface TestRunnerPlugin {
|
|||||||
name: string;
|
name: string;
|
||||||
setup?(config: FullConfig, configDir: string, reporter: Reporter): Promise<void>;
|
setup?(config: FullConfig, configDir: string, reporter: Reporter): Promise<void>;
|
||||||
begin?(suite: Suite): Promise<void>;
|
begin?(suite: Suite): Promise<void>;
|
||||||
|
end?(): Promise<void>;
|
||||||
teardown?(): Promise<void>;
|
teardown?(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ export function createPlugin(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
teardown: async () => {
|
end: async () => {
|
||||||
await new Promise(f => stoppableServer.stop(f));
|
await new Promise(f => stoppableServer.stop(f));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -112,7 +112,7 @@ class HtmlReporter implements Reporter {
|
|||||||
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
|
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onExit() {
|
async _onExit() {
|
||||||
if (process.env.CI || !this._buildResult)
|
if (process.env.CI || !this._buildResult)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ export class Multiplexer implements Reporter {
|
|||||||
await Promise.resolve().then(() => reporter.onEnd?.(result)).catch(e => console.error('Error in reporter', e));
|
await Promise.resolve().then(() => reporter.onEnd?.(result)).catch(e => console.error('Error in reporter', e));
|
||||||
|
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await Promise.resolve().then(() => (reporter as any).onExit?.()).catch(e => console.error('Error in reporter', e));
|
await Promise.resolve().then(() => (reporter as any)._onExit?.()).catch(e => console.error('Error in reporter', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
|
@ -6,3 +6,4 @@
|
|||||||
../third_party/
|
../third_party/
|
||||||
../plugins/
|
../plugins/
|
||||||
../util.ts
|
../util.ts
|
||||||
|
../utilsBundle.ts
|
||||||
|
@ -25,10 +25,10 @@ import { createTitleMatcher, errorWithFile } from '../util';
|
|||||||
import type { Matcher, TestFileFilter } from '../util';
|
import type { Matcher, TestFileFilter } from '../util';
|
||||||
import { buildProjectsClosure, collectFilesForProject, filterProjects } from './projectUtils';
|
import { buildProjectsClosure, collectFilesForProject, filterProjects } from './projectUtils';
|
||||||
import { requireOrImport } from '../common/transform';
|
import { requireOrImport } from '../common/transform';
|
||||||
import { buildFileSuiteForProject, filterByFocusedLine, filterOnly, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
|
import { buildFileSuiteForProject, filterByFocusedLine, filterByTestIds, filterOnly, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
|
||||||
import { filterForShard } from './testGroups';
|
import { filterForShard } from './testGroups';
|
||||||
|
|
||||||
export async function loadAllTests(config: FullConfigInternal, projectsToIgnore: Set<FullProjectInternal>, fileMatcher: Matcher, errors: TestError[]): Promise<Suite> {
|
export async function loadAllTests(mode: 'out-of-process' | 'in-process', config: FullConfigInternal, projectsToIgnore: Set<FullProjectInternal>, fileMatcher: Matcher, errors: TestError[]): Promise<Suite> {
|
||||||
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
||||||
|
|
||||||
let filesToRunByProject = new Map<FullProjectInternal, string[]>();
|
let filesToRunByProject = new Map<FullProjectInternal, string[]>();
|
||||||
@ -81,7 +81,7 @@ export async function loadAllTests(config: FullConfigInternal, projectsToIgnore:
|
|||||||
// Load all test files and create a preprocessed root. Child suites are files there.
|
// Load all test files and create a preprocessed root. Child suites are files there.
|
||||||
const fileSuits: Suite[] = [];
|
const fileSuits: Suite[] = [];
|
||||||
{
|
{
|
||||||
const loaderHost: LoaderHost = process.env.PW_TEST_OOP_LOADER ? new OutOfProcessLoaderHost(config) : new InProcessLoaderHost(config);
|
const loaderHost: LoaderHost = mode === 'out-of-process' ? new OutOfProcessLoaderHost(config) : new InProcessLoaderHost(config);
|
||||||
const allTestFiles = new Set<string>();
|
const allTestFiles = new Set<string>();
|
||||||
for (const files of filesToRunByProject.values())
|
for (const files of filesToRunByProject.values())
|
||||||
files.forEach(file => allTestFiles.add(file));
|
files.forEach(file => allTestFiles.add(file));
|
||||||
@ -125,7 +125,7 @@ export async function loadAllTests(config: FullConfigInternal, projectsToIgnore:
|
|||||||
return rootSuite;
|
return rootSuite;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createProjectSuite(fileSuits: Suite[], project: FullProjectInternal, options: { cliFileFilters: TestFileFilter[], cliTitleMatcher?: Matcher }, files: string[]): Promise<Suite | null> {
|
async function createProjectSuite(fileSuits: Suite[], project: FullProjectInternal, options: { cliFileFilters: TestFileFilter[], cliTitleMatcher?: Matcher, testIdMatcher?: Matcher }, files: string[]): Promise<Suite | null> {
|
||||||
const fileSuitesMap = new Map<string, Suite>();
|
const fileSuitesMap = new Map<string, Suite>();
|
||||||
for (const fileSuite of fileSuits)
|
for (const fileSuite of fileSuits)
|
||||||
fileSuitesMap.set(fileSuite._requireFile, fileSuite);
|
fileSuitesMap.set(fileSuite._requireFile, fileSuite);
|
||||||
@ -143,8 +143,9 @@ async function createProjectSuite(fileSuits: Suite[], project: FullProjectIntern
|
|||||||
projectSuite._addSuite(builtSuite);
|
projectSuite._addSuite(builtSuite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Filter tests to respect line/column filter.
|
|
||||||
filterByFocusedLine(projectSuite, options.cliFileFilters);
|
filterByFocusedLine(projectSuite, options.cliFileFilters);
|
||||||
|
filterByTestIds(projectSuite, options.testIdMatcher);
|
||||||
|
|
||||||
const grepMatcher = createTitleMatcher(project.grep);
|
const grepMatcher = createTitleMatcher(project.grep);
|
||||||
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
||||||
|
@ -69,6 +69,7 @@ export class OutOfProcessLoaderHost extends LoaderHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async stop() {
|
override async stop() {
|
||||||
|
await this._startPromise;
|
||||||
const result = await this._processHost.sendMessage({ method: 'serializeCompilationCache' }) as any;
|
const result = await this._processHost.sendMessage({ method: 'serializeCompilationCache' }) as any;
|
||||||
addToCompilationCache(result);
|
addToCompilationCache(result);
|
||||||
await this._processHost.stop();
|
await this._processHost.stop();
|
||||||
|
@ -31,11 +31,11 @@ import type { FullConfigInternal } from '../common/types';
|
|||||||
import { loadReporter } from './loadUtils';
|
import { loadReporter } from './loadUtils';
|
||||||
import type { BuiltInReporter } from '../common/configLoader';
|
import type { BuiltInReporter } from '../common/configLoader';
|
||||||
|
|
||||||
export async function createReporter(config: FullConfigInternal, list: boolean) {
|
export async function createReporter(config: FullConfigInternal, mode: 'list' | 'watch' | 'run') {
|
||||||
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
||||||
dot: list ? ListModeReporter : DotReporter,
|
dot: mode === 'list' ? ListModeReporter : DotReporter,
|
||||||
line: list ? ListModeReporter : LineReporter,
|
line: mode === 'list' ? ListModeReporter : LineReporter,
|
||||||
list: list ? ListModeReporter : ListReporter,
|
list: mode === 'list' ? ListModeReporter : ListReporter,
|
||||||
github: GitHubReporter,
|
github: GitHubReporter,
|
||||||
json: JSONReporter,
|
json: JSONReporter,
|
||||||
junit: JUnitReporter,
|
junit: JUnitReporter,
|
||||||
@ -43,18 +43,22 @@ export async function createReporter(config: FullConfigInternal, list: boolean)
|
|||||||
html: HtmlReporter,
|
html: HtmlReporter,
|
||||||
};
|
};
|
||||||
const reporters: Reporter[] = [];
|
const reporters: Reporter[] = [];
|
||||||
for (const r of config.reporter) {
|
if (mode === 'watch') {
|
||||||
const [name, arg] = r;
|
reporters.push(new WatchModeReporter());
|
||||||
if (name in defaultReporters) {
|
} else {
|
||||||
reporters.push(new defaultReporters[name as keyof typeof defaultReporters](arg));
|
for (const r of config.reporter) {
|
||||||
} else {
|
const [name, arg] = r;
|
||||||
const reporterConstructor = await loadReporter(config, name);
|
if (name in defaultReporters) {
|
||||||
reporters.push(new reporterConstructor(arg));
|
reporters.push(new defaultReporters[name as keyof typeof defaultReporters](arg));
|
||||||
|
} else {
|
||||||
|
const reporterConstructor = await loadReporter(config, name);
|
||||||
|
reporters.push(new reporterConstructor(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process.env.PW_TEST_REPORTER) {
|
||||||
|
const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER);
|
||||||
|
reporters.push(new reporterConstructor());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (process.env.PW_TEST_REPORTER) {
|
|
||||||
const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER);
|
|
||||||
reporters.push(new reporterConstructor());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const someReporterPrintsToStdio = reporters.some(r => {
|
const someReporterPrintsToStdio = reporters.some(r => {
|
||||||
@ -64,7 +68,7 @@ export async function createReporter(config: FullConfigInternal, list: boolean)
|
|||||||
if (reporters.length && !someReporterPrintsToStdio) {
|
if (reporters.length && !someReporterPrintsToStdio) {
|
||||||
// Add a line/dot/list-mode reporter for convenience.
|
// Add a line/dot/list-mode reporter for convenience.
|
||||||
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
||||||
if (list)
|
if (mode === 'list')
|
||||||
reporters.unshift(new ListModeReporter());
|
reporters.unshift(new ListModeReporter());
|
||||||
else
|
else
|
||||||
reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter());
|
reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter());
|
||||||
@ -99,3 +103,6 @@ export class ListModeReporter implements Reporter {
|
|||||||
console.error('\n' + formatError(this.config, error, false).message);
|
console.error('\n' + formatError(this.config, error, false).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WatchModeReporter extends LineReporter {
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import { createTaskRunner, createTaskRunnerForList } from './tasks';
|
|||||||
import type { TaskRunnerState } from './tasks';
|
import type { TaskRunnerState } from './tasks';
|
||||||
import type { FullConfigInternal } from '../common/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
|
import { runWatchModeLoop } from './watchMode';
|
||||||
|
|
||||||
export class Runner {
|
export class Runner {
|
||||||
private _config: FullConfigInternal;
|
private _config: FullConfigInternal;
|
||||||
@ -46,7 +47,7 @@ export class Runner {
|
|||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
async runAllTests(): Promise<FullResult['status']> {
|
async runAllTests(watchMode: boolean): Promise<FullResult['status']> {
|
||||||
const config = this._config;
|
const config = this._config;
|
||||||
const listOnly = config._internal.listOnly;
|
const listOnly = config._internal.listOnly;
|
||||||
const deadline = config.globalTimeout ? monotonicTime() + config.globalTimeout : 0;
|
const deadline = config.globalTimeout ? monotonicTime() + config.globalTimeout : 0;
|
||||||
@ -54,9 +55,9 @@ export class Runner {
|
|||||||
// Legacy webServer support.
|
// Legacy webServer support.
|
||||||
webServerPluginsForConfig(config).forEach(p => config._internal.plugins.push({ factory: p }));
|
webServerPluginsForConfig(config).forEach(p => config._internal.plugins.push({ factory: p }));
|
||||||
|
|
||||||
const reporter = await createReporter(config, listOnly);
|
const reporter = await createReporter(config, listOnly ? 'list' : watchMode ? 'watch' : 'run');
|
||||||
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter)
|
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter)
|
||||||
: createTaskRunner(config, reporter);
|
: createTaskRunner(config, reporter, watchMode);
|
||||||
|
|
||||||
const context: TaskRunnerState = {
|
const context: TaskRunnerState = {
|
||||||
config,
|
config,
|
||||||
@ -77,12 +78,16 @@ export class Runner {
|
|||||||
|
|
||||||
const taskStatus = await taskRunner.run(context, deadline);
|
const taskStatus = await taskRunner.run(context, deadline);
|
||||||
let status: FullResult['status'] = 'passed';
|
let status: FullResult['status'] = 'passed';
|
||||||
if (context.phases.find(p => p.dispatcher.hasWorkerErrors()) || context.rootSuite?.allTests().some(test => !test.ok()))
|
const failedTests = context.rootSuite?.allTests().filter(test => !test.ok()) || [];
|
||||||
|
if (context.phases.find(p => p.dispatcher.hasWorkerErrors()) || failedTests.length)
|
||||||
status = 'failed';
|
status = 'failed';
|
||||||
if (status === 'passed' && taskStatus !== 'passed')
|
if (status === 'passed' && taskStatus !== 'passed')
|
||||||
status = taskStatus;
|
status = taskStatus;
|
||||||
await reporter.onExit({ status });
|
await reporter.onExit({ status });
|
||||||
|
|
||||||
|
if (watchMode)
|
||||||
|
await runWatchModeLoop(config, failedTests);
|
||||||
|
|
||||||
// Calling process.exit() might truncate large stdout/stderr output.
|
// Calling process.exit() might truncate large stdout/stderr output.
|
||||||
// See https://github.com/nodejs/node/issues/6456.
|
// See https://github.com/nodejs/node/issues/6456.
|
||||||
// See https://github.com/nodejs/node/issues/12921
|
// See https://github.com/nodejs/node/issues/12921
|
||||||
|
@ -50,29 +50,42 @@ export type TaskRunnerState = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createTaskRunner(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TaskRunnerState> {
|
export function createTaskRunner(config: FullConfigInternal, reporter: Multiplexer, doNotTeardown: boolean): TaskRunner<TaskRunnerState> {
|
||||||
const taskRunner = new TaskRunner<TaskRunnerState>(reporter, config.globalTimeout);
|
const taskRunner = new TaskRunner<TaskRunnerState>(reporter, config.globalTimeout);
|
||||||
|
|
||||||
for (const plugin of config._internal.plugins)
|
for (const plugin of config._internal.plugins)
|
||||||
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
|
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin, doNotTeardown));
|
||||||
taskRunner.addTask('load tests', createLoadTask());
|
if (config.globalSetup || config.globalTeardown)
|
||||||
|
taskRunner.addTask('global setup', createGlobalSetupTask(doNotTeardown));
|
||||||
|
taskRunner.addTask('load tests', createLoadTask('in-process'));
|
||||||
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
|
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
|
||||||
taskRunner.addTask('prepare workers', createTestGroupsTask());
|
addCommonTasks(taskRunner, config);
|
||||||
for (const plugin of config._internal.plugins)
|
return taskRunner;
|
||||||
taskRunner.addTask('plugin begin', async ({ rootSuite }) => plugin.instance?.begin?.(rootSuite!));
|
}
|
||||||
|
|
||||||
|
export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: Multiplexer, projectsToIgnore?: Set<FullProjectInternal>, additionalFileMatcher?: Matcher): TaskRunner<TaskRunnerState> {
|
||||||
|
const taskRunner = new TaskRunner<TaskRunnerState>(reporter, config.globalTimeout);
|
||||||
|
taskRunner.addTask('load tests', createLoadTask('out-of-process', projectsToIgnore, additionalFileMatcher));
|
||||||
|
addCommonTasks(taskRunner, config);
|
||||||
|
return taskRunner;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommonTasks(taskRunner: TaskRunner<TaskRunnerState>, config: FullConfigInternal) {
|
||||||
|
taskRunner.addTask('create tasks', createTestGroupsTask());
|
||||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||||
reporter.onBegin?.(config, rootSuite!);
|
reporter.onBegin?.(config, rootSuite!);
|
||||||
return () => reporter.onEnd();
|
return () => reporter.onEnd();
|
||||||
});
|
});
|
||||||
if (config.globalSetup || config.globalTeardown)
|
for (const plugin of config._internal.plugins)
|
||||||
taskRunner.addTask('global setup', createGlobalSetupTask());
|
taskRunner.addTask('plugin begin', createPluginBeginTask(plugin));
|
||||||
|
taskRunner.addTask('start workers', createWorkersTask());
|
||||||
taskRunner.addTask('test suite', createRunTestsTask());
|
taskRunner.addTask('test suite', createRunTestsTask());
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForList(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TaskRunnerState> {
|
export function createTaskRunnerForList(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TaskRunnerState> {
|
||||||
const taskRunner = new TaskRunner<TaskRunnerState>(reporter, config.globalTimeout);
|
const taskRunner = new TaskRunner<TaskRunnerState>(reporter, config.globalTimeout);
|
||||||
taskRunner.addTask('load tests', createLoadTask());
|
taskRunner.addTask('load tests', createLoadTask('in-process'));
|
||||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||||
reporter.onBegin?.(config, rootSuite!);
|
reporter.onBegin?.(config, rootSuite!);
|
||||||
return () => reporter.onEnd();
|
return () => reporter.onEnd();
|
||||||
@ -80,23 +93,30 @@ export function createTaskRunnerForList(config: FullConfigInternal, reporter: Mu
|
|||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TaskRunnerState> {
|
function createPluginSetupTask(plugin: TestRunnerPluginRegistration, doNotTeardown: boolean): Task<TaskRunnerState> {
|
||||||
return async ({ config, reporter }) => {
|
return async ({ config, reporter }) => {
|
||||||
if (typeof plugin.factory === 'function')
|
if (typeof plugin.factory === 'function')
|
||||||
plugin.instance = await plugin.factory();
|
plugin.instance = await plugin.factory();
|
||||||
else
|
else
|
||||||
plugin.instance = plugin.factory;
|
plugin.instance = plugin.factory;
|
||||||
await plugin.instance?.setup?.(config, config._internal.configDir, reporter);
|
await plugin.instance?.setup?.(config, config._internal.configDir, reporter);
|
||||||
return () => plugin.instance?.teardown?.();
|
return doNotTeardown ? undefined : () => plugin.instance?.teardown?.();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGlobalSetupTask(): Task<TaskRunnerState> {
|
function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task<TaskRunnerState> {
|
||||||
|
return async ({ rootSuite }) => {
|
||||||
|
await plugin.instance?.begin?.(rootSuite!);
|
||||||
|
return () => plugin.instance?.end?.();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGlobalSetupTask(doNotTeardown: boolean): Task<TaskRunnerState> {
|
||||||
return async ({ config }) => {
|
return async ({ config }) => {
|
||||||
const setupHook = config.globalSetup ? await loadGlobalHook(config, config.globalSetup) : undefined;
|
const setupHook = config.globalSetup ? await loadGlobalHook(config, config.globalSetup) : undefined;
|
||||||
const teardownHook = config.globalTeardown ? await loadGlobalHook(config, config.globalTeardown) : undefined;
|
const teardownHook = config.globalTeardown ? await loadGlobalHook(config, config.globalTeardown) : undefined;
|
||||||
const globalSetupResult = setupHook ? await setupHook(config) : undefined;
|
const globalSetupResult = setupHook ? await setupHook(config) : undefined;
|
||||||
return async () => {
|
return doNotTeardown ? undefined : async () => {
|
||||||
if (typeof globalSetupResult === 'function')
|
if (typeof globalSetupResult === 'function')
|
||||||
await globalSetupResult();
|
await globalSetupResult();
|
||||||
await teardownHook?.(config);
|
await teardownHook?.(config);
|
||||||
@ -126,12 +146,12 @@ function createRemoveOutputDirsTask(): Task<TaskRunnerState> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLoadTask(projectsToIgnore = new Set<FullProjectInternal>(), additionalFileMatcher?: Matcher): Task<TaskRunnerState> {
|
function createLoadTask(mode: 'out-of-process' | 'in-process', projectsToIgnore = new Set<FullProjectInternal>(), additionalFileMatcher?: Matcher): Task<TaskRunnerState> {
|
||||||
return async (context, errors) => {
|
return async (context, errors) => {
|
||||||
const { config } = context;
|
const { config } = context;
|
||||||
const cliMatcher = config._internal.cliFileFilters.length ? createFileMatcherFromFilters(config._internal.cliFileFilters) : () => true;
|
const cliMatcher = config._internal.cliFileFilters.length ? createFileMatcherFromFilters(config._internal.cliFileFilters) : () => true;
|
||||||
const fileMatcher = (value: string) => cliMatcher(value) && (additionalFileMatcher ? additionalFileMatcher(value) : true);
|
const fileMatcher = (value: string) => cliMatcher(value) && (additionalFileMatcher ? additionalFileMatcher(value) : true);
|
||||||
context.rootSuite = await loadAllTests(config, projectsToIgnore, fileMatcher, errors);
|
context.rootSuite = await loadAllTests(mode, config, projectsToIgnore, fileMatcher, errors);
|
||||||
// Fail when no tests.
|
// Fail when no tests.
|
||||||
if (!context.rootSuite.allTests().length && !config._internal.passWithNoTests && !config.shard)
|
if (!context.rootSuite.allTests().length && !config._internal.passWithNoTests && !config.shard)
|
||||||
throw new Error(`No tests found`);
|
throw new Error(`No tests found`);
|
||||||
@ -158,9 +178,13 @@ function createTestGroupsTask(): Task<TaskRunnerState> {
|
|||||||
context.phases.push({ dispatcher: new Dispatcher(config, reporter), projects });
|
context.phases.push({ dispatcher: new Dispatcher(config, reporter), projects });
|
||||||
context.config._internal.maxConcurrentTestGroups = Math.max(context.config._internal.maxConcurrentTestGroups, testGroupsInPhase);
|
context.config._internal.maxConcurrentTestGroups = Math.max(context.config._internal.maxConcurrentTestGroups, testGroupsInPhase);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWorkersTask(): Task<TaskRunnerState> {
|
||||||
|
return async ({ phases }) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
for (const { dispatcher } of context.phases.reverse())
|
for (const { dispatcher } of phases.reverse())
|
||||||
await dispatcher.stop();
|
await dispatcher.stop();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
275
packages/playwright-test/src/runner/watchMode.ts
Normal file
275
packages/playwright-test/src/runner/watchMode.ts
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/**
|
||||||
|
* Copyright Microsoft Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import readline from 'readline';
|
||||||
|
import { ManualPromise } from 'playwright-core/lib/utils';
|
||||||
|
import type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||||
|
import { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
import { createFileFilterForArg, createFileMatcherFromFilters, createTitleMatcher, forceRegExp } from '../util';
|
||||||
|
import type { Matcher } from '../util';
|
||||||
|
import { createTaskRunnerForWatch } from './tasks';
|
||||||
|
import type { TaskRunnerState } from './tasks';
|
||||||
|
import { buildProjectsClosure, filterProjects } from './projectUtils';
|
||||||
|
import { clearCompilationCache } from '../common/compilationCache';
|
||||||
|
import type { FullResult, TestCase } from 'packages/playwright-test/reporter';
|
||||||
|
import chokidar from 'chokidar';
|
||||||
|
import { WatchModeReporter } from './reporters';
|
||||||
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
|
import { enquirer } from '../utilsBundle';
|
||||||
|
|
||||||
|
class FSWatcher {
|
||||||
|
private _dirtyFiles = new Set<string>();
|
||||||
|
private _notifyDirtyFiles: (() => void) | undefined;
|
||||||
|
|
||||||
|
constructor(dirs: string[]) {
|
||||||
|
let timer: NodeJS.Timer;
|
||||||
|
chokidar.watch(dirs, { ignoreInitial: true }).on('all', async (event, file) => {
|
||||||
|
if (event !== 'add' && event !== 'change')
|
||||||
|
return;
|
||||||
|
this._dirtyFiles.add(file);
|
||||||
|
if (timer)
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
this._notifyDirtyFiles?.();
|
||||||
|
}, 250);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onDirtyFiles(): Promise<void> {
|
||||||
|
if (this._dirtyFiles.size)
|
||||||
|
return;
|
||||||
|
await new Promise<void>(f => this._notifyDirtyFiles = f);
|
||||||
|
}
|
||||||
|
|
||||||
|
takeDirtyFiles(): Set<string> {
|
||||||
|
const result = this._dirtyFiles;
|
||||||
|
this._dirtyFiles = new Set();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runWatchModeLoop(config: FullConfigInternal, failedTests: TestCase[]) {
|
||||||
|
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
||||||
|
const projectClosure = buildProjectsClosure(projects);
|
||||||
|
config._internal.passWithNoTests = true;
|
||||||
|
const failedTestIdCollector = new Set(failedTests.map(t => t.id));
|
||||||
|
|
||||||
|
const originalTitleMatcher = config._internal.cliTitleMatcher;
|
||||||
|
const originalFileFilters = config._internal.cliFileFilters;
|
||||||
|
|
||||||
|
const fsWatcher = new FSWatcher(projectClosure.map(p => p.testDir));
|
||||||
|
let lastFilePattern: string | undefined;
|
||||||
|
let lastTestPattern: string | undefined;
|
||||||
|
while (true) {
|
||||||
|
process.stdout.write(`
|
||||||
|
Waiting for file changes...
|
||||||
|
${colors.dim('press')} ${colors.bold('h')} ${colors.dim('to show help, press')} ${colors.bold('q')} ${colors.dim('to quit')}
|
||||||
|
`);
|
||||||
|
const readCommandPromise = readCommand();
|
||||||
|
await Promise.race([
|
||||||
|
fsWatcher.onDirtyFiles(),
|
||||||
|
readCommandPromise,
|
||||||
|
]);
|
||||||
|
if (!readCommandPromise.isDone())
|
||||||
|
readCommandPromise.resolve('changed');
|
||||||
|
|
||||||
|
const command = await readCommandPromise;
|
||||||
|
if (command === 'changed') {
|
||||||
|
process.stdout.write('\x1Bc');
|
||||||
|
await runChangedTests(config, failedTestIdCollector, projectClosure, fsWatcher.takeDirtyFiles());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (command === 'all') {
|
||||||
|
process.stdout.write('\x1Bc');
|
||||||
|
// All means reset filters.
|
||||||
|
config._internal.cliTitleMatcher = originalTitleMatcher;
|
||||||
|
config._internal.cliFileFilters = originalFileFilters;
|
||||||
|
lastFilePattern = undefined;
|
||||||
|
lastTestPattern = undefined;
|
||||||
|
await runTests(config, failedTestIdCollector);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (command === 'file') {
|
||||||
|
const { filePattern } = await enquirer.prompt<{ filePattern: string }>({
|
||||||
|
type: 'text',
|
||||||
|
name: 'filePattern',
|
||||||
|
message: 'Input filename pattern (regex)',
|
||||||
|
initial: lastFilePattern,
|
||||||
|
});
|
||||||
|
if (filePattern.trim()) {
|
||||||
|
lastFilePattern = filePattern;
|
||||||
|
config._internal.cliFileFilters = [createFileFilterForArg(filePattern)];
|
||||||
|
} else {
|
||||||
|
lastFilePattern = undefined;
|
||||||
|
config._internal.cliFileFilters = originalFileFilters;
|
||||||
|
}
|
||||||
|
await runTests(config, failedTestIdCollector);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (command === 'grep') {
|
||||||
|
const { testPattern } = await enquirer.prompt<{ testPattern: string }>({
|
||||||
|
type: 'text',
|
||||||
|
name: 'testPattern',
|
||||||
|
message: 'Input test name pattern (regex)',
|
||||||
|
initial: lastTestPattern,
|
||||||
|
});
|
||||||
|
if (testPattern.trim()) {
|
||||||
|
lastTestPattern = testPattern;
|
||||||
|
config._internal.cliTitleMatcher = createTitleMatcher(forceRegExp(testPattern));
|
||||||
|
} else {
|
||||||
|
lastTestPattern = undefined;
|
||||||
|
config._internal.cliTitleMatcher = originalTitleMatcher;
|
||||||
|
}
|
||||||
|
await runTests(config, failedTestIdCollector);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (command === 'failed') {
|
||||||
|
process.stdout.write('\x1Bc');
|
||||||
|
config._internal.testIdMatcher = id => failedTestIdCollector.has(id);
|
||||||
|
try {
|
||||||
|
await runTests(config, failedTestIdCollector);
|
||||||
|
} finally {
|
||||||
|
config._internal.testIdMatcher = undefined;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runChangedTests(config: FullConfigInternal, failedTestIds: Set<string>, projectClosure: FullProjectInternal[], files: Set<string>) {
|
||||||
|
const commandLineFileMatcher = config._internal.cliFileFilters.length ? createFileMatcherFromFilters(config._internal.cliFileFilters) : () => true;
|
||||||
|
|
||||||
|
// Collect projects with changes.
|
||||||
|
const filesByProject = new Map<FullProjectInternal, string[]>();
|
||||||
|
for (const project of projectClosure) {
|
||||||
|
const projectFiles: string[] = [];
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file.startsWith(project.testDir))
|
||||||
|
continue;
|
||||||
|
if (project._internal.type === 'dependency' || commandLineFileMatcher(file))
|
||||||
|
projectFiles.push(file);
|
||||||
|
}
|
||||||
|
if (projectFiles.length)
|
||||||
|
filesByProject.set(project, projectFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all the affected projects, follow project dependencies.
|
||||||
|
// Prepare to exclude all the projects that do not depend on this file, as if they did not exist.
|
||||||
|
const affectedProjects = affectedProjectsClosure(projectClosure, [...filesByProject.keys()]);
|
||||||
|
const affectsAnyDependency = [...affectedProjects].some(p => p._internal.type === 'dependency');
|
||||||
|
const projectsToIgnore = new Set(projectClosure.filter(p => !affectedProjects.has(p)));
|
||||||
|
|
||||||
|
// If there are affected dependency projects, do the full run, respect the original CLI.
|
||||||
|
// if there are no affected dependency projects, intersect CLI with dirty files
|
||||||
|
const additionalFileMatcher = affectsAnyDependency ? () => true : (file: string) => files.has(file);
|
||||||
|
return await runTests(config, failedTestIds, projectsToIgnore, additionalFileMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTests(config: FullConfigInternal, failedTestIds: Set<string>, projectsToIgnore?: Set<FullProjectInternal>, additionalFileMatcher?: Matcher) {
|
||||||
|
const reporter = new Multiplexer([new WatchModeReporter()]);
|
||||||
|
const taskRunner = createTaskRunnerForWatch(config, reporter, projectsToIgnore, additionalFileMatcher);
|
||||||
|
const context: TaskRunnerState = {
|
||||||
|
config,
|
||||||
|
reporter,
|
||||||
|
phases: [],
|
||||||
|
};
|
||||||
|
clearCompilationCache();
|
||||||
|
reporter.onConfigure(config);
|
||||||
|
const taskStatus = await taskRunner.run(context, 0);
|
||||||
|
let status: FullResult['status'] = 'passed';
|
||||||
|
|
||||||
|
let hasFailedTests = false;
|
||||||
|
for (const test of context.rootSuite?.allTests() || []) {
|
||||||
|
if (test.outcome() === 'expected') {
|
||||||
|
failedTestIds.delete(test.id);
|
||||||
|
} else {
|
||||||
|
failedTestIds.add(test.id);
|
||||||
|
hasFailedTests = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.phases.find(p => p.dispatcher.hasWorkerErrors()) || hasFailedTests)
|
||||||
|
status = 'failed';
|
||||||
|
if (status === 'passed' && taskStatus !== 'passed')
|
||||||
|
status = taskStatus;
|
||||||
|
await reporter.onExit({ status });
|
||||||
|
}
|
||||||
|
|
||||||
|
function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set<FullProjectInternal> {
|
||||||
|
const result = new Set<FullProjectInternal>(affected);
|
||||||
|
for (let i = 0; i < projectClosure.length; ++i) {
|
||||||
|
for (const p of projectClosure) {
|
||||||
|
for (const dep of p._internal.deps) {
|
||||||
|
if (result.has(dep))
|
||||||
|
result.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCommand(): ManualPromise<Command> {
|
||||||
|
const result = new ManualPromise<Command>();
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 });
|
||||||
|
readline.emitKeypressEvents(process.stdin, rl);
|
||||||
|
if (process.stdin.isTTY)
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
|
||||||
|
const handler = (text: string, key: any) => {
|
||||||
|
if (text === '\x03' || text === '\x1B' || (key && key.name === 'escape') || (key && key.ctrl && key.name === 'c'))
|
||||||
|
return process.exit(130);
|
||||||
|
if (process.platform !== 'win32' && key && key.ctrl && key.name === 'z') {
|
||||||
|
process.kill(process.ppid, 'SIGTSTP');
|
||||||
|
process.kill(process.pid, 'SIGTSTP');
|
||||||
|
}
|
||||||
|
const name = key?.name;
|
||||||
|
if (name === 'q')
|
||||||
|
process.exit(0);
|
||||||
|
if (name === 'h') {
|
||||||
|
process.stdout.write(`
|
||||||
|
Watch Usage
|
||||||
|
${commands.map(i => colors.dim(' press ') + colors.reset(colors.bold(i[0])) + colors.dim(` to ${i[1]}`)).join('\n')}
|
||||||
|
`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case 'a': result.resolve('all'); break;
|
||||||
|
case 'p': result.resolve('file'); break;
|
||||||
|
case 't': result.resolve('grep'); break;
|
||||||
|
case 'f': result.resolve('failed'); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.stdin.on('keypress', handler);
|
||||||
|
result.finally(() => {
|
||||||
|
process.stdin.off('keypress', handler);
|
||||||
|
rl.close();
|
||||||
|
if (process.stdin.isTTY)
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command = 'all' | 'failed' | 'changed' | 'file' | 'grep';
|
||||||
|
|
||||||
|
const commands = [
|
||||||
|
['a', 'rerun all tests'],
|
||||||
|
['f', 'rerun only failed tests'],
|
||||||
|
['p', 'filter by a filename'],
|
||||||
|
['t', 'filter by a test name regex pattern'],
|
||||||
|
['q', 'quit'],
|
||||||
|
];
|
@ -106,6 +106,15 @@ export type TestFileFilter = {
|
|||||||
column: number | null;
|
column: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function createFileFilterForArg(arg: string): TestFileFilter {
|
||||||
|
const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
|
||||||
|
return {
|
||||||
|
re: forceRegExp(match ? match[1] : arg),
|
||||||
|
line: match ? parseInt(match[2], 10) : null,
|
||||||
|
column: match?.[3] ? parseInt(match[3], 10) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createFileMatcherFromFilters(filters: TestFileFilter[]): Matcher {
|
export function createFileMatcherFromFilters(filters: TestFileFilter[]): Matcher {
|
||||||
return createFileMatcher(filters.map(filter => filter.re || filter.exact || ''));
|
return createFileMatcher(filters.map(filter => filter.re || filter.exact || ''));
|
||||||
}
|
}
|
||||||
@ -174,7 +183,7 @@ export function forceRegExp(pattern: string): RegExp {
|
|||||||
const match = pattern.match(/^\/(.*)\/([gi]*)$/);
|
const match = pattern.match(/^\/(.*)\/([gi]*)$/);
|
||||||
if (match)
|
if (match)
|
||||||
return new RegExp(match[1], match[2]);
|
return new RegExp(match[1], match[2]);
|
||||||
return new RegExp(pattern, 'g');
|
return new RegExp(pattern, 'gi');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function relativeFilePath(file: string): string {
|
export function relativeFilePath(file: string): string {
|
||||||
|
@ -19,3 +19,4 @@ export const open: typeof import('../bundles/utils/node_modules/open') = require
|
|||||||
export const pirates: typeof import('../bundles/utils/node_modules/pirates') = require('./utilsBundleImpl').pirates;
|
export const pirates: typeof import('../bundles/utils/node_modules/pirates') = require('./utilsBundleImpl').pirates;
|
||||||
export const sourceMapSupport: typeof import('../bundles/utils/node_modules/@types/source-map-support') = require('./utilsBundleImpl').sourceMapSupport;
|
export const sourceMapSupport: typeof import('../bundles/utils/node_modules/@types/source-map-support') = require('./utilsBundleImpl').sourceMapSupport;
|
||||||
export const stoppable: typeof import('../bundles/utils/node_modules/@types/stoppable') = require('./utilsBundleImpl').stoppable;
|
export const stoppable: typeof import('../bundles/utils/node_modules/@types/stoppable') = require('./utilsBundleImpl').stoppable;
|
||||||
|
export const enquirer: typeof import('../bundles/utils/node_modules/enquirer') = require('./utilsBundleImpl').enquirer;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user