feat(electron): experimental electron support (#2166)

This commit is contained in:
Pavel Feldman 2020-05-11 18:00:33 -07:00 committed by GitHub
parent a2bee2ca73
commit ffe7084652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1028 additions and 1 deletions

View File

@ -15,7 +15,10 @@
*/
const { Playwright } = require('./lib/server/playwright');
const { Electron } = require('./lib/server/electron');
const path = require('path');
const playwrightRoot = path.join(__dirname, 'packages', 'playwright');
module.exports = new Playwright(playwrightRoot, require(path.join(playwrightRoot, 'browsers.json'))['browsers']);
const playwright = new Playwright(playwrightRoot, require(path.join(playwrightRoot, 'browsers.json'))['browsers']);
playwright.electron = new Electron();
module.exports = playwright;

513
package-lock.json generated
View File

@ -30,6 +30,38 @@
"js-tokens": "^4.0.0"
}
},
"@electron/get": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.2.tgz",
"integrity": "sha512-vAuHUbfvBQpYTJ5wB7uVIDq5c/Ry0fiTBMs7lnEYAo/qXXppIVcWdfBr57u6eRnKdVso7KSiH6p/LbQAG6Izrg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"global-agent": "^2.0.2",
"global-tunnel-ng": "^2.7.1",
"got": "^9.6.0",
"progress": "^2.0.3",
"sanitize-filename": "^1.6.2",
"sumchecker": "^3.0.1"
}
},
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
"dev": true
},
"@szmarczak/http-timer": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
"integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
"dev": true,
"requires": {
"defer-to-connect": "^1.0.1"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -810,6 +842,13 @@
"integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==",
"dev": true
},
"boolean": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
"integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==",
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1012,6 +1051,29 @@
"unset-value": "^1.0.0"
}
},
"cacheable-request": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
"integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
"dev": true,
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^3.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"responselike": "^1.0.2"
},
"dependencies": {
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"dev": true
}
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1250,6 +1312,15 @@
}
}
},
"clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"dev": true,
"requires": {
"mimic-response": "^1.0.0"
}
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -1328,6 +1399,17 @@
"typedarray": "^0.0.6"
}
},
"config-chain": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
"integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
"dev": true,
"optional": true,
"requires": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"console-browserify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
@ -1371,6 +1453,13 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"core-js": {
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -1497,12 +1586,37 @@
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"decompress-response": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
"dev": true,
"requires": {
"mimic-response": "^1.0.0"
}
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"defer-to-connect": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
"integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
"dev": true
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"optional": true,
"requires": {
"object-keys": "^1.0.12"
}
},
"define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
@ -1560,6 +1674,13 @@
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
"dev": true
},
"detect-node": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
"dev": true,
"optional": true
},
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@ -1594,6 +1715,12 @@
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
"dev": true
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
"dev": true
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@ -1606,6 +1733,52 @@
"stream-shift": "^1.0.0"
}
},
"electron": {
"version": "9.0.0-beta.24",
"resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.24.tgz",
"integrity": "sha512-25L3XMqm/1CCaV5CgU5ZkhKXw9830WeipJrTW0+VC5XTKp/3xHwhxyQ5G1kQnOTJd7IGwOamvw237D6e1YKnng==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
"@types/node": "^12.0.12",
"extract-zip": "^1.0.3"
},
"dependencies": {
"@types/node": {
"version": "12.12.38",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.38.tgz",
"integrity": "sha512-75eLjX0pFuTcUXnnWmALMzzkYorjND0ezNEycaKesbUBg9eGZp4GHPuDmkRc4mQQvIpe29zrzATNRA6hkYqwmA==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"extract-zip": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"dev": true,
"requires": {
"concat-stream": "^1.6.2",
"debug": "^2.6.9",
"mkdirp": "^0.5.4",
"yauzl": "^2.10.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
@ -1641,6 +1814,13 @@
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"dev": true,
"optional": true
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -1666,6 +1846,12 @@
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
"dev": true
},
"env-paths": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
"dev": true
},
"errno": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@ -1675,6 +1861,13 @@
"prr": "~1.0.1"
}
},
"es6-error": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true,
"optional": true
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
@ -2317,6 +2510,17 @@
"readable-stream": "^2.0.0"
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@ -2393,6 +2597,31 @@
"is-glob": "^4.0.1"
}
},
"global-agent": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.8.tgz",
"integrity": "sha512-VpBe/rhY6Rw2VDOTszAMNambg+4Qv8j0yiTNDYEXXXxkUNGWLHp8A3ztK4YDBbFNcWF4rgsec6/5gPyryya/+A==",
"dev": true,
"optional": true,
"requires": {
"boolean": "^3.0.0",
"core-js": "^3.6.4",
"es6-error": "^4.1.1",
"matcher": "^2.1.0",
"roarr": "^2.15.2",
"semver": "^7.1.2",
"serialize-error": "^5.0.0"
},
"dependencies": {
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true,
"optional": true
}
}
},
"global-modules": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@ -2428,6 +2657,19 @@
"which": "^1.2.14"
}
},
"global-tunnel-ng": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz",
"integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==",
"dev": true,
"optional": true,
"requires": {
"encodeurl": "^1.0.2",
"lodash": "^4.17.10",
"npm-conf": "^1.1.3",
"tunnel": "^0.0.6"
}
},
"globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
@ -2437,6 +2679,46 @@
"type-fest": "^0.8.1"
}
},
"globalthis": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.1.tgz",
"integrity": "sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==",
"dev": true,
"optional": true,
"requires": {
"define-properties": "^1.1.3"
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
"integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
"dev": true,
"requires": {
"@sindresorhus/is": "^0.14.0",
"@szmarczak/http-timer": "^1.1.2",
"cacheable-request": "^6.0.0",
"decompress-response": "^3.3.0",
"duplexer3": "^0.1.4",
"get-stream": "^4.1.0",
"lowercase-keys": "^1.0.1",
"mimic-response": "^1.0.1",
"p-cancelable": "^1.0.0",
"to-readable-stream": "^1.0.0",
"url-parse-lax": "^3.0.0"
},
"dependencies": {
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
"pump": "^3.0.0"
}
}
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@ -2561,6 +2843,12 @@
"parse-passwd": "^1.0.0"
}
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
"dev": true
},
"https-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
@ -2932,6 +3220,12 @@
"esprima": "^4.0.0"
}
},
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
"integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
"dev": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@ -2950,6 +3244,13 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true,
"optional": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
@ -2959,6 +3260,24 @@
"minimist": "^1.2.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6"
}
},
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
"integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
"dev": true,
"requires": {
"json-buffer": "3.0.0"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -3017,6 +3336,12 @@
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -3068,6 +3393,25 @@
"object-visit": "^1.0.0"
}
},
"matcher": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz",
"integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==",
"dev": true,
"optional": true,
"requires": {
"escape-string-regexp": "^2.0.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true,
"optional": true
}
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -3145,6 +3489,12 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"dev": true
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@ -3356,6 +3706,32 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==",
"dev": true
},
"npm-conf": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz",
"integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==",
"dev": true,
"optional": true,
"requires": {
"config-chain": "^1.1.11",
"pify": "^3.0.0"
},
"dependencies": {
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true,
"optional": true
}
}
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -3402,6 +3778,13 @@
}
}
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"optional": true
},
"object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@ -3474,6 +3857,12 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"p-cancelable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
"integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
"dev": true
},
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@ -3670,6 +4059,12 @@
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"prepend-http": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
"dev": true
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -3693,6 +4088,13 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
"dev": true,
"optional": true
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -4024,6 +4426,15 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
},
"responselike": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
"integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
"dev": true,
"requires": {
"lowercase-keys": "^1.0.0"
}
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@ -4058,6 +4469,30 @@
"inherits": "^2.0.1"
}
},
"roarr": {
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz",
"integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==",
"dev": true,
"optional": true,
"requires": {
"boolean": "^3.0.0",
"detect-node": "^2.0.4",
"globalthis": "^1.0.1",
"json-stringify-safe": "^5.0.1",
"semver-compare": "^1.0.0",
"sprintf-js": "^1.1.2"
},
"dependencies": {
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
"dev": true,
"optional": true
}
}
},
"run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@ -4103,6 +4538,15 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"sanitize-filename": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
"integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
"dev": true,
"requires": {
"truncate-utf8-bytes": "^1.0.0"
}
},
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@ -4120,6 +4564,23 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
"dev": true,
"optional": true
},
"serialize-error": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz",
"integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==",
"dev": true,
"optional": true,
"requires": {
"type-fest": "^0.8.0"
}
},
"serialize-javascript": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
@ -4532,6 +4993,15 @@
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
"dev": true
},
"sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
"dev": true,
"requires": {
"debug": "^4.1.0"
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -4700,6 +5170,12 @@
}
}
},
"to-readable-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
"integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
"dev": true
},
"to-regex": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
@ -4721,6 +5197,15 @@
"is-number": "^7.0.0"
}
},
"truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
"integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
"dev": true,
"requires": {
"utf8-byte-length": "^1.0.1"
}
},
"ts-loader": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.2.tgz",
@ -4755,6 +5240,13 @@
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"dev": true,
"optional": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@ -4812,6 +5304,12 @@
"imurmurhash": "^0.1.4"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
},
"unset-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@ -4891,12 +5389,27 @@
}
}
},
"url-parse-lax": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
"integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
"dev": true,
"requires": {
"prepend-http": "^2.0.0"
}
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
},
"utf8-byte-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
"integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=",
"dev": true
},
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",

View File

@ -64,6 +64,7 @@
"colors": "^1.4.0",
"commonmark": "^0.28.1",
"cross-env": "^5.0.5",
"electron": "^9.0.0-beta.24",
"eslint": "^6.6.0",
"esprima": "^4.0.0",
"formidable": "^1.2.1",

View File

@ -0,0 +1,2 @@
# playwright-electron
This package contains the [Electron](https://www.electronjs.org/) flavor of [Playwright](http://github.com/microsoft/playwright).

View File

@ -0,0 +1,3 @@
{
"browsers": []
}

20
packages/playwright-electron/index.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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 * as types from 'playwright-core/types/types';
export * from 'playwright-core/types/types';
export const electron: types.BrowserType<types.Electron>;

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const { Playwright } = require('playwright-core/lib/server/playwright');
const { Electron } = require('playwright-core/lib/server/electron');
const playwright = new Playwright(__dirname, require('./browsers.json')['browsers']);
playwright.electron = new Electron();
module.exports = playwright;

View File

@ -0,0 +1,16 @@
{
"name": "playwright-electron",
"version": "0.1.0",
"description": "A high-level API to automate Electron",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
"main": "index.js",
"scripts": {},
"author": {
"name": "Microsoft Corporation"
},
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "^1.0.1"
}
}

198
src/server/electron.ts Normal file
View File

@ -0,0 +1,198 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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 * as path from 'path';
import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser';
import { CRConnection, CRSession } from '../chromium/crConnection';
import { CRExecutionContext } from '../chromium/crExecutionContext';
import { TimeoutError } from '../errors';
import { Events } from '../events';
import { ExtendedEventEmitter } from '../extendedEventEmitter';
import { helper } from '../helper';
import * as js from '../javascript';
import { InnerLogger, Logger, RootLogger } from '../logger';
import { Page } from '../page';
import { TimeoutSettings } from '../timeoutSettings';
import { WebSocketTransport } from '../transport';
import * as types from '../types';
import { BrowserServer } from './browserServer';
import { launchProcess, waitForLine } from './processLauncher';
import { BrowserContext } from '../browserContext';
type ElectronLaunchOptions = {
args?: string[],
cwd?: string,
env?: {[key: string]: string|number|boolean},
handleSIGINT?: boolean,
handleSIGTERM?: boolean,
handleSIGHUP?: boolean,
timeout?: number,
logger?: Logger,
};
export const ElectronEvents = {
ElectronApplication: {
Close: 'close',
Window: 'window',
}
};
export class ElectronApplication extends ExtendedEventEmitter {
private _logger: InnerLogger;
private _browserContext: CRBrowserContext;
private _nodeConnection: CRConnection;
private _nodeSession: CRSession;
private _nodeExecutionContext: js.ExecutionContext | undefined;
private _nodeElectronHandle: js.JSHandle<any> | undefined;
private _windows = new Set<Page>();
private _lastWindowId = 0;
readonly _timeoutSettings = new TimeoutSettings();
constructor(logger: InnerLogger, browser: CRBrowser, nodeConnection: CRConnection) {
super();
this._logger = logger;
this._browserContext = browser._defaultContext!;
this._browserContext.on(Events.BrowserContext.Close, () => this.emit(ElectronEvents.ElectronApplication.Close));
this._browserContext.on(Events.BrowserContext.Page, event => this._onPage(event));
this._nodeConnection = nodeConnection;
this._nodeSession = nodeConnection.rootSession;
}
private async _onPage(page: Page) {
// Needs to be sync.
const windowId = ++this._lastWindowId;
// Can be async.
const handle = await this._nodeElectronHandle!.evaluateHandle(({ BrowserWindow }, windowId) => BrowserWindow.fromId(windowId), windowId);
(page as any).browserWindow = handle;
(page as any)._browserWindowId = windowId;
page.on(Events.Page.Close, () => {
(page as any).browserWindow.dispose();
this._windows.delete(page);
});
this._windows.add(page);
this.emit(ElectronEvents.ElectronApplication.Window, page);
}
windows(): Page[] {
return [...this._windows];
}
async newBrowserWindow(options: any): Promise<Page> {
const windowId = await this.evaluate(async ({ BrowserWindow }, options) => {
const win = new BrowserWindow(options);
win.loadURL('about:blank');
return win.id;
}, options);
for (const page of this._windows) {
if ((page as any)._browserWindowId === windowId)
return page;
}
return await this.waitForEvent(ElectronEvents.ElectronApplication.Window, (page: Page) => (page as any)._browserWindowId === windowId);
}
context(): BrowserContext {
return this._browserContext;
}
async close() {
await this.evaluate(({ app }) => app.quit());
this._nodeConnection.close();
}
async _init() {
this._nodeSession.once('Runtime.executionContextCreated', event => {
this._nodeExecutionContext = new js.ExecutionContext(new CRExecutionContext(this._nodeSession, event.context), this._logger);
});
await this._nodeSession.send('Runtime.enable', {}).catch(e => {});
this._nodeElectronHandle = await this._nodeExecutionContext!.evaluateHandleInternal(() => {
// Resolving the race between the debugger and the boot-time script.
if ((global as any)._playwrightRun)
return (global as any)._playwrightRun();
return new Promise(f => (global as any)._playwrightRunCallback = f);
});
}
async evaluate<R, Arg>(pageFunction: types.FuncOn<any, Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: types.FuncOn<any, void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: types.FuncOn<any, Arg, R>, arg: Arg): Promise<R> {
return this._nodeElectronHandle!.evaluate(pageFunction, arg);
}
async evaluateHandle<R, Arg>(pageFunction: types.FuncOn<any, Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: types.FuncOn<any, void, R>, arg?: any): Promise<types.SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: types.FuncOn<any, Arg, R>, arg: Arg): Promise<types.SmartHandle<R>> {
return this._nodeElectronHandle!.evaluateHandle(pageFunction, arg);
}
protected _computeDeadline(options?: types.TimeoutOptions): number {
return this._timeoutSettings.computeDeadline(options);
}
}
export class Electron {
async launch(executablePath: string, options: ElectronLaunchOptions = {}): Promise<ElectronApplication> {
const {
args = [],
env = process.env,
handleSIGINT = true,
handleSIGTERM = true,
handleSIGHUP = true,
timeout = 30000,
} = options;
const deadline = TimeoutSettings.computeDeadline(timeout);
let app: ElectronApplication | undefined = undefined;
const logger = new RootLogger(options.logger);
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', '--require', path.join(__dirname, 'electronLoader.js'), ...args];
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath,
args: electronArguments,
env,
handleSIGINT,
handleSIGTERM,
handleSIGHUP,
logger,
pipe: true,
cwd: options.cwd,
omitDownloads: true,
attemptToGracefullyClose: () => app!.close(),
onkill: (exitCode, signal) => {
if (app)
app.emit(ElectronEvents.ElectronApplication.Close, exitCode, signal);
},
});
const timeoutError = new TimeoutError(`Timed out while trying to connect to Electron!`);
const nodeMatch = await waitForLine(launchedProcess, launchedProcess.stderr, /^Debugger listening on (ws:\/\/.*)$/, helper.timeUntilDeadline(deadline), timeoutError);
const nodeConnection = await WebSocketTransport.connect(nodeMatch[1], transport => {
return new CRConnection(transport, logger);
});
const chromeMatch = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, helper.timeUntilDeadline(deadline), timeoutError);
const chromeTransport = await WebSocketTransport.connect(chromeMatch[1], transport => {
return transport;
});
const browserServer = new BrowserServer(launchedProcess, gracefullyClose, null);
const browser = await CRBrowser.connect(chromeTransport, true, logger, { ...options, viewport: null });
browser._ownedServer = browserServer;
app = new ElectronApplication(logger, browser, nodeConnection);
await app._init();
return app;
}
}

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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 * as electron from 'electron';
// Electron loader is passed to Electron as --require electronLoader.js and
// defers the app ready event until remote debugging is established.
(async () => {
const app = electron.app;
const originalEmitMethod = app.emit.bind(app);
const originalIsReadyMethod = app.isReady.bind(app);
const originalWhenReadyMethod = app.whenReady.bind(app);
const deferredEmits: { event: string | symbol, args: any[] }[] = [];
let attached = false;
let isReady = false;
let readyCallback: () => void;
const readyPromise = new Promise(f => readyCallback = f);
app.isReady = () => {
if (attached)
return originalIsReadyMethod();
return isReady;
};
app.whenReady = async () => {
if (attached)
return originalWhenReadyMethod();
await readyPromise;
};
app.emit = (event: string | symbol, ...args: any[]): boolean => {
if (attached)
return originalEmitMethod(event, ...args);
deferredEmits.push({ event, args });
return true;
};
const playwrightRun = () => {
while (deferredEmits.length) {
const emit = deferredEmits.shift();
if (emit!.event === 'ready') {
isReady = true;
readyCallback();
}
originalEmitMethod(emit!.event, ...emit!.args);
}
attached = true;
return electron;
};
// Resolving the race between the debugger and the boot-time script.
if ((global as any)._playwrightRunCallback) {
(global as any)._playwrightRunCallback(playwrightRun());
return;
}
(global as any)._playwrightRun = playwrightRun;
})();

View File

@ -0,0 +1,165 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const path = require('path');
const config = require('../test.config');
const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron';
describe('Electron', function() {
beforeEach(async (state, testRun) => {
const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName);
state.application = await playwright.electron.launch(electronPath, {
args: [path.join(__dirname, 'testApp.js')],
logger: {
isEnabled: (name, severity) => {
return name === 'browser' ||
(name === 'protocol' && config.dumpProtocolOnFailure);
},
log: (name, severity, message, args) => {
if (name === 'browser') {
if (severity === 'warning')
testRun.log(`\x1b[31m[browser]\x1b[0m ${message}`)
else
testRun.log(`\x1b[33m[browser]\x1b[0m ${message}`)
} else if (name === 'protocol' && config.dumpProtocolOnFailure) {
testRun.log(`\x1b[32m[protocol]\x1b[0m ${message}`)
}
}
}
});
});
afterEach(async (state, testRun) => {
await state.application.close();
if (config.dumpProtocolOnFailure) {
if (testRun.ok())
testRun.output().splice(0);
}
});
it('should script application', async ({ application }) => {
const appPath = await application.evaluate(async ({ app }) => app.getAppPath());
expect(appPath).toContain('electron');
});
it('should create window', async ({ application }) => {
const [ page ] = await Promise.all([
application.waitForEvent('window'),
application.evaluate(({ BrowserWindow }) => {
const window = new BrowserWindow({ width: 800, height: 600 });
window.loadURL('data:text/html,<title>Hello World 1</title>');
})
]);
await page.waitForLoadState('domcontentloaded');
expect(await page.title()).toBe('Hello World 1');
});
it('should create window 2', async ({ application }) => {
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await page.goto('data:text/html,<title>Hello World 2</title>');
expect(await page.title()).toBe('Hello World 2');
});
it('should create multiple windows', async ({ application }) => {
const createPage = async ordinal => {
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await Promise.all([
page.waitForNavigation(),
page.browserWindow.evaluate((window, ordinal) => window.loadURL(`data:text/html,<title>Hello World ${ordinal}</title>`), ordinal)
]);
return page;
};
const page1 = await createPage(1);
const page2 = await createPage(2);
const page3 = await createPage(3);
await page1.close();
const page4 = await createPage(4);
const titles = [];
for (const window of application.windows())
titles.push(await window.title());
expect(titles).toEqual(['Hello World 2', 'Hello World 3', 'Hello World 4']);
});
it('should route network', async ({ application }) => {
await application.context().route('**/empty.html', (route, request) => {
route.fulfill({
status: 200,
contentType: 'text/html',
body: '<title>Hello World</title>',
})
});
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await page.goto('https://localhost:1000/empty.html');
expect(await page.title()).toBe('Hello World');
});
it('should support init script', async ({ application }) => {
await application.context().addInitScript('window.magic = 42;')
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await page.goto('data:text/html,<script>window.copy = magic</script>');
expect(await page.evaluate(() => copy)).toBe(42);
});
it('should expose function', async ({ application }) => {
const result = new Promise(f => callback = f);
const t = Date.now();
await application.context().exposeFunction('add', (a, b) => a + b);
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await page.goto('data:text/html,<script>window.result = add(20, 22);</script>');
expect(await page.evaluate(() => result)).toBe(42);
});
});
describe('Electron window', function() {
beforeAll(async state => {
const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName);
state.application = await playwright.electron.launch(electronPath, {
args: [path.join(__dirname, 'testApp.js')]
});
});
afterAll(async state => {
await state.application.close();
});
beforeEach(async state => {
state.page = await state.application.newBrowserWindow({ width: 800, height: 600 });
});
afterEach(async state => {
await state.page.close();
});
it('should click the button', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked');
});
it('should check the box', async({page}) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
await page.check('input');
expect(await page.evaluate(() => checkbox.checked)).toBe(true);
});
it('should not check the checked box', async({page}) => {
await page.setContent(`<input id='checkbox' type='checkbox' checked></input>`);
await page.check('input');
expect(await page.evaluate(() => checkbox.checked)).toBe(true);
});
it('should type into a textarea', async({page, server}) => {
await page.evaluate(() => {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.focus();
});
const text = 'Hello world. I am the text that was typed!';
await page.keyboard.type(text);
expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe(text);
});
});

3
test/electron/testApp.js Normal file
View File

@ -0,0 +1,3 @@
const { app } = require('electron');
app.on('window-all-closed', e => e.preventDefault());

View File

@ -229,6 +229,15 @@ module.exports = {
environments: [customEnvironment],
},
{
files: [
'./electron/electron.spec.js',
],
browsers: ['chromium'],
title: '[Electron]',
environments: [customEnvironment],
},
{
files: [
'./apicoverage.spec.js',