mirror of
https://github.com/Llewellynvdm/nativefier.git
synced 2024-12-22 01:58:54 +00:00
Add playwright integration testing to the app (PR #1397)
This PR allows us to code playwright integration tests that can potentially replace some of our manual tests and allow us automated testing of the app itself. Current technical limitations: * No app level keyboard simulation support (e.g, zoom in, zoom out, etc.) * No code coverage support, so even though we are testing the app, the code coverage does not reflect this fact
This commit is contained in:
parent
c42c63a8b0
commit
e664bc6af8
53
.github/manual-test
vendored
53
.github/manual-test
vendored
@ -60,10 +60,6 @@ node ./lib/cli.js 'https://npmjs.com/' \
|
||||
"$tmp_dir"
|
||||
|
||||
printf '\n***** SMOKE TEST 1: Test checklist *****
|
||||
- Injected js: should show an alert saying hello
|
||||
- Injected css: should make npmjs all blue
|
||||
- Internal links open internally
|
||||
- External links open in browser
|
||||
- Context menu -> Open Link In New Window works
|
||||
- MAC ONLY: Context menu -> Open Link In New Tab works
|
||||
- Keyboard shortcuts: {back, forward, zoom in/out/zero} work
|
||||
@ -73,52 +69,15 @@ request_feedback "$tmp_dir"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
printf "\n***** SMOKE TEST 2: Setting up test and building app... *****\n"
|
||||
tmp_dir=$(mktemp -d -t nativefier-manual-test-auth-XXXXX)
|
||||
name="nativefier-smoke-test-2"
|
||||
# Removing for now as httpbin is not presently up
|
||||
# node ./lib/cli.js 'http://httpbin.org/basic-auth/foo/bar' \
|
||||
node ./lib/cli.js 'https://authenticationtest.com/HTTPAuth/' \
|
||||
--basic-auth-username user \
|
||||
--basic-auth-password pass \
|
||||
--name "$name" \
|
||||
"$tmp_dir"
|
||||
|
||||
printf '\n***** SMOKE TEST 2: Test checklist *****
|
||||
- Was successfully logged in via HTTP Basic Auth. Should see a "Login Success" and a green banner.
|
||||
- Console: no Electron runtime deprecation warnings/error logged'
|
||||
launch_app "$tmp_dir" "$name"
|
||||
request_feedback "$tmp_dir"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
printf '\n***** SMOKE TEST 3: Setting up test and building app... *****\n'
|
||||
tmp_dir=$(mktemp -d -t nativefier-manual-test-auth-prompt-XXXXX)
|
||||
name='nativefier-smoke-test-3'
|
||||
# node ./lib/cli.js 'http://httpbin.org/basic-auth/foo/bar' \
|
||||
node ./lib/cli.js 'https://authenticationtest.com/HTTPAuth/' \
|
||||
--name "$name" \
|
||||
"$tmp_dir"
|
||||
|
||||
printf '\n***** SMOKE TEST 3: Test checklist *****
|
||||
- Should get a login window. Log in with username="user" and password="pass".
|
||||
- Post login, you should see a "Login Success" and a green banner.
|
||||
- Console: no Electron runtime deprecation warnings/error logged'
|
||||
|
||||
launch_app "$tmp_dir" "$name"
|
||||
request_feedback "$tmp_dir"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
printf '\n***** SMOKE TEST 4: Setting up test and building app... *****\n'
|
||||
printf '\n***** SMOKE TEST 2: Setting up test and building app... *****\n'
|
||||
tmp_dir=$(mktemp -d -t nativefier-manual-test-tray-XXXXX)
|
||||
name='nativefier-smoke-test-4'
|
||||
name='nativefier-smoke-test-2'
|
||||
node ./lib/cli.js 'https://google.com/' \
|
||||
--name "$name" \
|
||||
--tray \
|
||||
"$tmp_dir"
|
||||
|
||||
printf '\n***** SMOKE TEST 4: Test checklist *****
|
||||
printf '\n***** SMOKE TEST 2: Test checklist *****
|
||||
- Should have an app with a tray icon
|
||||
- Console: no Electron runtime deprecation warnings/error logged'
|
||||
|
||||
@ -127,15 +86,15 @@ request_feedback "$tmp_dir"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
printf '\n***** SMOKE TEST 5: Setting up test and building app... *****\n'
|
||||
printf '\n***** SMOKE TEST 3: Setting up test and building app... *****\n'
|
||||
tmp_dir=$(mktemp -d -t nativefier-manual-test-start-in-tray-XXXXX)
|
||||
name='nativefier-smoke-test-5'
|
||||
name='nativefier-smoke-test-3'
|
||||
node ./lib/cli.js 'https://google.com/' \
|
||||
--name "$name" \
|
||||
--tray start-in-tray \
|
||||
"$tmp_dir"
|
||||
|
||||
printf '\n***** SMOKE TEST 5: Test checklist *****
|
||||
printf '\n***** SMOKE TEST 3: Test checklist *****
|
||||
- Should have an app that does not show a window initially,
|
||||
but will have a tray icon that will show the window.
|
||||
- Console: no Electron runtime deprecation warnings/error logged'
|
||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -39,8 +39,12 @@ jobs:
|
||||
package-lock.json
|
||||
app/package-lock.json
|
||||
# Will also (through `prepare` hook): 1. install ./app, and 2. build
|
||||
- run: npm ci --no-fund
|
||||
- env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
run: npm ci --no-fund
|
||||
# Only run linter once, for faster CI. Align the versions of Node here with above and publish.yml.
|
||||
- if: matrix.platform == 'ubuntu-latest' && matrix.node-version == '17'
|
||||
run: npm run lint
|
||||
- run: npm test
|
||||
- run: npm run test -- --testPathIgnorePatterns ".*playwright.*"
|
||||
- if: matrix.platform != 'ubuntu-latest' # Doesn't work on non-GUI ubuntu
|
||||
run: npm run test:playwright
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,8 @@ app/dist/*
|
||||
built-tests
|
||||
|
||||
# commit a placeholder to keep the app/lib directory
|
||||
app/inject
|
||||
!app/inject/_placeholder
|
||||
!app/lib/.placeholder
|
||||
|
||||
dist
|
||||
|
206
app/npm-shrinkwrap.json
generated
206
app/npm-shrinkwrap.json
generated
@ -64,9 +64,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz",
|
||||
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
|
||||
"version": "16.11.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
|
||||
"integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
@ -270,16 +270,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/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==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
||||
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"object-keys": "^1.0.12"
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
@ -296,9 +300,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "18.0.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-18.0.3.tgz",
|
||||
"integrity": "sha512-QRUZkGL8O/8CyDmTLSjBeRsZmGTPlPVeWnnpkdNqgHYYaOc/A881FKMiNzvQ9Cj0a+rUavDdwBUfUL82U3Ay7w==",
|
||||
"version": "18.0.4",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-18.0.4.tgz",
|
||||
"integrity": "sha512-xfsozNpFr3WzeM1EFlw2qqiqXbCrgQNBJJMlcC4/DUYVpkF8364SZenX7FFFA42NmwXiOEahkvvho/u7UrAcGg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
@ -517,6 +521,28 @@
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
@ -548,19 +574,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/global-agent/node_modules/semver": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
|
||||
"integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==",
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.4.0"
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/global-tunnel-ng": {
|
||||
@ -623,6 +649,45 @@
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
|
||||
@ -723,13 +788,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz",
|
||||
"integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
@ -1186,6 +1254,13 @@
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
@ -1231,9 +1306,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.26.tgz",
|
||||
"integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==",
|
||||
"version": "16.11.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
|
||||
"integrity": "sha512-C1pD3kgLoZ56Uuy5lhfOxie4aZlA3UMGLX9rXteq4WitEZH6Rl80mwactt9QG0w0gLFlN/kLBTFnGXtDVWvWQw==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
@ -1389,13 +1464,14 @@
|
||||
"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==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
|
||||
"integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"object-keys": "^1.0.12"
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"detect-node": {
|
||||
@ -1412,9 +1488,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron": {
|
||||
"version": "18.0.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-18.0.3.tgz",
|
||||
"integrity": "sha512-QRUZkGL8O/8CyDmTLSjBeRsZmGTPlPVeWnnpkdNqgHYYaOc/A881FKMiNzvQ9Cj0a+rUavDdwBUfUL82U3Ay7w==",
|
||||
"version": "18.0.4",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-18.0.4.tgz",
|
||||
"integrity": "sha512-xfsozNpFr3WzeM1EFlw2qqiqXbCrgQNBJJMlcC4/DUYVpkF8364SZenX7FFFA42NmwXiOEahkvvho/u7UrAcGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@electron/get": "^1.13.0",
|
||||
@ -1591,6 +1667,25 @@
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
@ -1616,13 +1711,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
|
||||
"integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==",
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"lru-cache": "^7.4.0"
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1675,6 +1770,33 @@
|
||||
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-property-descriptors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"http-cache-semantics": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
|
||||
@ -1759,11 +1881,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz",
|
||||
"integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"matcher": {
|
||||
"version": "3.0.0",
|
||||
@ -2123,6 +2248,13 @@
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
|
@ -4,8 +4,9 @@ import {
|
||||
NewWindowWebContentsEvent,
|
||||
} from 'electron';
|
||||
import contextMenu from 'electron-context-menu';
|
||||
import log from 'loglevel';
|
||||
|
||||
import { nativeTabsSupported, openExternal } from '../helpers/helpers';
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
import { setupNativefierWindow } from '../helpers/windowEvents';
|
||||
import { createNewWindow } from '../helpers/windowHelpers';
|
||||
import {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
|
||||
export async function createLoginWindow(
|
||||
loginCallback: (username?: string, password?: string) => void,
|
||||
parent?: BrowserWindow,
|
||||
|
@ -3,14 +3,17 @@ import * as path from 'path';
|
||||
|
||||
import { ipcMain, BrowserWindow, Event } from 'electron';
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import log from 'loglevel';
|
||||
|
||||
import { initContextMenu } from './contextMenu';
|
||||
import { createMenu } from './menu';
|
||||
import {
|
||||
getAppIcon,
|
||||
getCounterValue,
|
||||
isOSX,
|
||||
nativeTabsSupported,
|
||||
} from '../helpers/helpers';
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
import { IS_PLAYWRIGHT } from '../helpers/playwrightHelpers';
|
||||
import { onNewWindow, setupNativefierWindow } from '../helpers/windowEvents';
|
||||
import {
|
||||
clearCache,
|
||||
@ -18,8 +21,6 @@ import {
|
||||
getDefaultWindowOptions,
|
||||
hideWindow,
|
||||
} from '../helpers/windowHelpers';
|
||||
import { initContextMenu } from './contextMenu';
|
||||
import { createMenu } from './menu';
|
||||
import {
|
||||
OutputOptions,
|
||||
outputOptionsToWindowOptions,
|
||||
@ -77,6 +78,11 @@ export async function createMainWindow(
|
||||
...getDefaultWindowOptions(outputOptionsToWindowOptions(options)),
|
||||
});
|
||||
|
||||
// Just load about:blank to start, gives playwright something to latch onto initially for testing.
|
||||
if (IS_PLAYWRIGHT) {
|
||||
await mainWindow.loadURL('about:blank');
|
||||
}
|
||||
|
||||
mainWindowState.manage(mainWindow);
|
||||
|
||||
// after first run, no longer force maximize to be true
|
||||
|
@ -8,9 +8,9 @@ import {
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import { cleanupPlainText, isOSX, openExternal } from '../helpers/helpers';
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
import {
|
||||
clearAppData,
|
||||
getCurrentURL,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { app, Tray, Menu, ipcMain, nativeImage, BrowserWindow } from 'electron';
|
||||
import log from 'loglevel';
|
||||
|
||||
import { getAppIcon, getCounterValue, isOSX } from '../helpers/helpers';
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
import { OutputOptions } from '../../../shared/src/options/model';
|
||||
|
||||
export function createTrayIcon(
|
||||
|
@ -3,7 +3,8 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { BrowserWindow, OpenExternalOptions, shell } from 'electron';
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import * as log from '../helpers/loggingHelper';
|
||||
|
||||
export const INJECT_DIR = path.join(__dirname, '..', 'inject');
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as fs from 'fs';
|
||||
import log from 'loglevel';
|
||||
import * as path from 'path';
|
||||
|
||||
import { isOSX, isWindows, isLinux } from './helpers';
|
||||
import * as log from './loggingHelper';
|
||||
|
||||
type fsError = Error & { code: string };
|
||||
|
||||
|
82
app/src/helpers/loggingHelper.ts
Normal file
82
app/src/helpers/loggingHelper.ts
Normal file
@ -0,0 +1,82 @@
|
||||
// This helper allows logs to either be printed to the console as they would normally or if
|
||||
// the USE_LOG_FILE environment variable is set (such as through our playwright tests), then
|
||||
// the logs can be diverted from the command line to a log file, so that they can be displayed
|
||||
// later (such as at the end of a playwright test run to help diagnose potential failures).
|
||||
// Use this instead of loglevel whenever logging messages inside the app.
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import loglevel from 'loglevel';
|
||||
|
||||
import { safeGetEnv } from './playwrightHelpers';
|
||||
|
||||
const USE_LOG_FILE = safeGetEnv('USE_LOG_FILE') === '1';
|
||||
const LOG_FILE_DIR = safeGetEnv('LOG_FILE_DIR') ?? process.cwd();
|
||||
const LOG_FILENAME = path.join(LOG_FILE_DIR, `${new Date().getTime()}.log`);
|
||||
|
||||
const logLevelNames = ['TRACE', 'DEBUG', 'INFO ', 'WARN ', 'ERROR'];
|
||||
|
||||
function _logger(
|
||||
logFunc: (...args: unknown[]) => void,
|
||||
level: loglevel.LogLevelNumbers,
|
||||
...args: unknown[]
|
||||
): void {
|
||||
if (USE_LOG_FILE && loglevel.getLevel() >= level) {
|
||||
for (const arg of args) {
|
||||
try {
|
||||
const lines =
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
JSON.stringify(arg, null, 2)?.split('\n') ?? `${arg}`.split('\n');
|
||||
for (const line of lines) {
|
||||
fs.appendFileSync(
|
||||
LOG_FILENAME,
|
||||
`${new Date().getTime()} ${logLevelNames[level]} ${line}\n`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
fs.appendFileSync(LOG_FILENAME, `${logLevelNames[level]} ${arg}\n`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logFunc(...args);
|
||||
}
|
||||
}
|
||||
|
||||
export function debug(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.debug, loglevel.levels.DEBUG, ...args);
|
||||
}
|
||||
|
||||
export function error(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.error, loglevel.levels.ERROR, ...args);
|
||||
}
|
||||
|
||||
export function info(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.info, loglevel.levels.INFO, ...args);
|
||||
}
|
||||
|
||||
export function log(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.info, loglevel.levels.INFO, ...args);
|
||||
}
|
||||
|
||||
export function setLevel(
|
||||
level: loglevel.LogLevelDesc,
|
||||
persist?: boolean,
|
||||
): void {
|
||||
loglevel.setLevel(level, persist);
|
||||
}
|
||||
|
||||
export function trace(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.trace, loglevel.levels.TRACE, ...args);
|
||||
}
|
||||
|
||||
export function warn(...args: unknown[]): void {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
_logger(loglevel.warn, loglevel.levels.WARN, ...args);
|
||||
}
|
6
app/src/helpers/playwrightHelpers.ts
Normal file
6
app/src/helpers/playwrightHelpers.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const IS_PLAYWRIGHT = safeGetEnv('PLAYWRIGHT_TEST') === '1';
|
||||
export const PLAYWRIGHT_CONFIG = safeGetEnv('PLAYWRIGHT_CONFIG');
|
||||
|
||||
export function safeGetEnv(key: string): string | undefined {
|
||||
return key in process.env ? process.env[key] : undefined;
|
||||
}
|
@ -5,10 +5,9 @@ import {
|
||||
NewWindowWebContentsEvent,
|
||||
WebContents,
|
||||
} from 'electron';
|
||||
import log from 'loglevel';
|
||||
import { WindowOptions } from '../../../shared/src/options/model';
|
||||
|
||||
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
|
||||
import * as log from './loggingHelper';
|
||||
import {
|
||||
blockExternalURL,
|
||||
createAboutBlankWindow,
|
||||
@ -17,6 +16,7 @@ import {
|
||||
sendParamsOnDidFinishLoad,
|
||||
setProxyRules,
|
||||
} from './windowHelpers';
|
||||
import { WindowOptions } from '../../../shared/src/options/model';
|
||||
|
||||
export function onNewWindow(
|
||||
options: WindowOptions,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
dialog,
|
||||
BrowserWindow,
|
||||
@ -8,10 +10,9 @@ import {
|
||||
OnResponseStartedListenerDetails,
|
||||
} from 'electron';
|
||||
|
||||
import log from 'loglevel';
|
||||
import path from 'path';
|
||||
import { TrayValue, WindowOptions } from '../../../shared/src/options/model';
|
||||
import { getCSSToInject, isOSX, nativeTabsSupported } from './helpers';
|
||||
import * as log from './loggingHelper';
|
||||
import { TrayValue, WindowOptions } from '../../../shared/src/options/model';
|
||||
|
||||
const ZOOM_INTERVAL = 0.1;
|
||||
|
||||
|
@ -12,17 +12,22 @@ import electron, {
|
||||
Event,
|
||||
} from 'electron';
|
||||
import electronDownload from 'electron-dl';
|
||||
import * as log from 'loglevel';
|
||||
|
||||
import { createLoginWindow } from './components/loginWindow';
|
||||
import {
|
||||
createMainWindow,
|
||||
saveAppArgs,
|
||||
APP_ARGS_FILE_PATH,
|
||||
createMainWindow,
|
||||
} from './components/mainWindow';
|
||||
import { createTrayIcon } from './components/trayIcon';
|
||||
import { isOSX, removeUserAgentSpecifics } from './helpers/helpers';
|
||||
import { inferFlashPath } from './helpers/inferFlash';
|
||||
import * as log from './helpers/loggingHelper';
|
||||
import {
|
||||
IS_PLAYWRIGHT,
|
||||
PLAYWRIGHT_CONFIG,
|
||||
safeGetEnv,
|
||||
} from './helpers/playwrightHelpers';
|
||||
import { setupNativefierWindow } from './helpers/windowEvents';
|
||||
import {
|
||||
OutputOptions,
|
||||
@ -34,17 +39,21 @@ if (require('electron-squirrel-startup')) {
|
||||
app.exit();
|
||||
}
|
||||
|
||||
if (process.argv.indexOf('--verbose') > -1) {
|
||||
if (process.argv.indexOf('--verbose') > -1 || safeGetEnv('VERBOSE') === '1') {
|
||||
log.setLevel('DEBUG');
|
||||
process.traceDeprecation = true;
|
||||
process.traceProcessWarnings = true;
|
||||
process.argv.slice(1);
|
||||
}
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
|
||||
const appArgs = JSON.parse(
|
||||
fs.readFileSync(APP_ARGS_FILE_PATH, 'utf8'),
|
||||
) as OutputOptions;
|
||||
const appArgs =
|
||||
IS_PLAYWRIGHT && PLAYWRIGHT_CONFIG
|
||||
? (JSON.parse(PLAYWRIGHT_CONFIG) as OutputOptions)
|
||||
: (JSON.parse(
|
||||
fs.readFileSync(APP_ARGS_FILE_PATH, 'utf8'),
|
||||
) as OutputOptions);
|
||||
|
||||
log.debug('appArgs', appArgs);
|
||||
// Do this relatively early so that we can start storing appData with the app
|
||||
@ -182,7 +191,7 @@ const setDockBadge = isOSX()
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
log.debug('app.window-all-closed');
|
||||
if (!isOSX() || appArgs.fastQuit) {
|
||||
if (!isOSX() || appArgs.fastQuit || IS_PLAYWRIGHT) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
@ -243,7 +252,7 @@ if (appArgs.widevine) {
|
||||
|
||||
app.on('activate', (event: electron.Event, hasVisibleWindows: boolean) => {
|
||||
log.debug('app.activate', { event, hasVisibleWindows });
|
||||
if (isOSX()) {
|
||||
if (isOSX() && !IS_PLAYWRIGHT) {
|
||||
// this is called when the dock is clicked
|
||||
if (!hasVisibleWindows) {
|
||||
mainWindow.show();
|
||||
|
1286
npm-shrinkwrap.json
generated
1286
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -46,6 +46,7 @@
|
||||
"relock": "rm -rf ./node_modules/ ./app/node_modules/ ./npm-shrinkwrap.json ./app/npm-shrinkwrap.json && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out; cd app && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out",
|
||||
"test:integration": "jest --testRegex '.*integration-test.js'",
|
||||
"test:manual": "npm run build && bash .github/manual-test",
|
||||
"test:playwright": "jest --testRegex '.*playwright-test.js'",
|
||||
"test:unit": "jest",
|
||||
"test:watch": "echo 'Remember to run npm run build:watch for the test watcher to work!' && jest --watchAll --collectCoverage=false",
|
||||
"test:withlog": "LOGLEVEL=trace npm run test",
|
||||
@ -78,10 +79,12 @@
|
||||
"@types/tmp": "^0.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
"@typescript-eslint/parser": "^5.3.0",
|
||||
"electron": "^18.0.3",
|
||||
"eslint": "^8.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.0.6",
|
||||
"playwright": "^1.20.1",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-loader": "^9.2.3",
|
||||
@ -95,6 +98,14 @@
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"./app/dist/**/*.js",
|
||||
"./lib/**/*.js",
|
||||
"./shared/lib/**/*.js"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"[.-]test.js$"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^electron$": "<rootDir>/app/dist/mocks/electron.js"
|
||||
},
|
||||
@ -103,19 +114,18 @@
|
||||
],
|
||||
"testEnvironment": "node",
|
||||
"testPathIgnorePatterns": [
|
||||
"<rootDir>/src.*",
|
||||
"<rootDir>/node_modules.*",
|
||||
"<rootDir>/app/node_modules.*",
|
||||
"<rootDir>/app/src.*",
|
||||
"<rootDir>/app/lib.*",
|
||||
"<rootDir>/app/node_modules.*"
|
||||
"<rootDir>/src.*"
|
||||
],
|
||||
"watchPathIgnorePatterns": [
|
||||
"<rootDir>/src.*",
|
||||
"<rootDir>/tsconfig-base.json",
|
||||
"<rootDir>/app/src.*",
|
||||
"<rootDir>/app/lib.*",
|
||||
"<rootDir>/app/src.*",
|
||||
"<rootDir>/app/tsconfig.json",
|
||||
"<rootDir>/shared/tsconfig.json"
|
||||
"<rootDir>/shared/tsconfig.json",
|
||||
"<rootDir>/src.*",
|
||||
"<rootDir>/tsconfig-base.json"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
|
@ -3,6 +3,7 @@ import * as path from 'path';
|
||||
export const DEFAULT_APP_NAME = 'APP';
|
||||
|
||||
// Update both DEFAULT_ELECTRON_VERSION and DEFAULT_CHROME_VERSION together,
|
||||
// and update package.json / devDeps / electron to value of DEFAULT_ELECTRON_VERSION
|
||||
// and update app / package.json / devDeps / electron to value of DEFAULT_ELECTRON_VERSION
|
||||
export const DEFAULT_ELECTRON_VERSION = '18.0.3';
|
||||
// https://atom.io/download/atom-shell/index.json
|
||||
|
@ -5,3 +5,5 @@ if (process.env.LOGLEVEL) {
|
||||
} else {
|
||||
log.disableAll();
|
||||
}
|
||||
|
||||
process.traceDeprecation = true;
|
||||
|
401
src/playwright-test.ts
Normal file
401
src/playwright-test.ts
Normal file
@ -0,0 +1,401 @@
|
||||
import { once } from 'events';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { Shell } from 'electron';
|
||||
import {
|
||||
_electron,
|
||||
ConsoleMessage,
|
||||
Dialog,
|
||||
ElectronApplication,
|
||||
Page,
|
||||
} from 'playwright';
|
||||
|
||||
import { getTempDir } from './helpers/helpers';
|
||||
import { NativefierOptions } from '../shared/src/options/model';
|
||||
|
||||
const INJECT_DIR = path.join(__dirname, '..', 'app', 'inject');
|
||||
|
||||
const log = console;
|
||||
|
||||
function sleep(milliseconds: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Application launch', () => {
|
||||
jest.setTimeout(60000);
|
||||
|
||||
let app: ElectronApplication;
|
||||
let appClosed = true;
|
||||
|
||||
const appMainJSPath = path.join(__dirname, '..', 'app', 'lib', 'main.js');
|
||||
const DEFAULT_CONFIG: NativefierOptions = {
|
||||
targetUrl: 'https://npmjs.com',
|
||||
};
|
||||
|
||||
const logFileDir = getTempDir('playwright');
|
||||
|
||||
const metaOrAlt = process.platform === 'darwin' ? 'Meta' : 'Alt';
|
||||
const metaOrCtrl = process.platform === 'darwin' ? 'Meta' : 'Control';
|
||||
|
||||
const spawnApp = async (
|
||||
playwrightConfig: NativefierOptions = { ...DEFAULT_CONFIG },
|
||||
awaitFirstWindow = true,
|
||||
preventNavigation = false,
|
||||
): Promise<Page | undefined> => {
|
||||
const consoleListener = (consoleMessage: ConsoleMessage): void => {
|
||||
const consoleMethods: Record<string, (...args: unknown[]) => unknown> = {
|
||||
debug: log.debug.bind(console),
|
||||
error: log.error.bind(console),
|
||||
info: log.info.bind(console),
|
||||
log: log.log.bind(console),
|
||||
trace: log.trace.bind(console),
|
||||
warn: log.warn.bind(console),
|
||||
};
|
||||
Promise.all(consoleMessage.args().map((x) => x.jsonValue()))
|
||||
.then((args) => {
|
||||
if (consoleMessage.type() in consoleMethods) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
consoleMethods[consoleMessage.type()]('window.console', args);
|
||||
} else {
|
||||
log.log('window.console', args);
|
||||
}
|
||||
})
|
||||
.catch(() => log.log('window.console', consoleMessage));
|
||||
};
|
||||
app = await _electron.launch({
|
||||
args: [appMainJSPath],
|
||||
env: {
|
||||
LOG_FILE_DIR: logFileDir,
|
||||
PLAYWRIGHT_TEST: '1',
|
||||
PLAYWRIGHT_CONFIG: JSON.stringify(playwrightConfig),
|
||||
USE_LOG_FILE: '1',
|
||||
VERBOSE: '1',
|
||||
},
|
||||
});
|
||||
app.on('window', (page: Page) => {
|
||||
page.on('console', consoleListener);
|
||||
if (preventNavigation) {
|
||||
// Prevent page navigation so we can have a reliable test
|
||||
page
|
||||
.route('*', (route): void => {
|
||||
log.info(`Preventing route: ${route.request().url()}`);
|
||||
route.abort().catch((error) => {
|
||||
log.error('ERROR', error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error('ERROR', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
app.on('close', () => (appClosed = true));
|
||||
appClosed = false;
|
||||
if (!awaitFirstWindow) {
|
||||
return undefined;
|
||||
}
|
||||
const window = await app.firstWindow();
|
||||
// Wait for our initial page to finish loading, otherwise some tests will break
|
||||
let waited = 0;
|
||||
while (
|
||||
window.url() === 'about:blank' &&
|
||||
playwrightConfig.targetUrl !== 'about:blank' &&
|
||||
waited < 2000
|
||||
) {
|
||||
waited += 100;
|
||||
await sleep(100);
|
||||
}
|
||||
return window;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
nukeInjects();
|
||||
nukeLogs(logFileDir);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (app && !appClosed) {
|
||||
await app.close();
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
showLogs(logFileDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('shows an initial window', async () => {
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
expect(app.windows()).toHaveLength(1);
|
||||
expect(await mainWindow.title()).toBe('npm');
|
||||
});
|
||||
|
||||
test('can inject some CSS', async () => {
|
||||
const fuschia = 'rgb(255, 0, 255)';
|
||||
createInject(
|
||||
'inject.css',
|
||||
`* { background-color: ${fuschia} !important; }`,
|
||||
);
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
const headerStyle = await mainWindow.$eval('header', (el) =>
|
||||
window.getComputedStyle(el),
|
||||
);
|
||||
expect(headerStyle.backgroundColor).toBe(fuschia);
|
||||
|
||||
await mainWindow.click('#nav-products-link');
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
const headerStylePostNavigate = await mainWindow.$eval('header', (el) =>
|
||||
window.getComputedStyle(el),
|
||||
);
|
||||
expect(headerStylePostNavigate.backgroundColor).toBe(fuschia);
|
||||
});
|
||||
|
||||
test('can inject some JS', async () => {
|
||||
const alertMsg = 'hello world from inject';
|
||||
createInject(
|
||||
'inject.js',
|
||||
`setTimeout(() => {alert("${alertMsg}"); }, 5000);`, // Buy ourselves 5 seconds to get the dialog handler setup
|
||||
);
|
||||
const mainWindow = (await spawnApp(
|
||||
{ ...DEFAULT_CONFIG },
|
||||
true,
|
||||
true,
|
||||
)) as Page;
|
||||
const [dialogPromise] = (await once(
|
||||
mainWindow,
|
||||
'dialog',
|
||||
)) as unknown as Promise<Dialog>[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const dialog: Dialog = await dialogPromise;
|
||||
await dialog.dismiss();
|
||||
expect(dialog.message()).toBe(alertMsg);
|
||||
});
|
||||
|
||||
test('can open internal links', async () => {
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
await mainWindow.click('#nav-products-link');
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
expect(app.windows()).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('tries to open external links', async () => {
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Install the mock first
|
||||
await app.evaluate(({ shell }: { shell: Shell }) => {
|
||||
// @ts-expect-error injecting into shell so that this promise
|
||||
// can be accessed outside of this anonymous function's scope
|
||||
// Not my favorite thing to do, but I could not find another way
|
||||
process.openExternalPromise = new Promise((resolve) => {
|
||||
shell.openExternal = async (url: string): Promise<void> => {
|
||||
resolve(url);
|
||||
return Promise.resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Click, but don't await it - Playwright waits for stuff that does not happen when Electron does openExternal.
|
||||
mainWindow
|
||||
.click('#footer > div:nth-child(2) > ul > li:nth-child(2) > a')
|
||||
.catch((err: unknown) => {
|
||||
expect(err).toBeUndefined();
|
||||
});
|
||||
|
||||
// Go pull out our value returned by our hacky global promise
|
||||
const openExternalUrl = await app.evaluate('process.openExternalPromise');
|
||||
expect(openExternalUrl).not.toBe('https://www.npmjs.com/');
|
||||
|
||||
expect(openExternalUrl).not.toBe(DEFAULT_CONFIG.targetUrl);
|
||||
});
|
||||
|
||||
// Currently disabled. Playwright doesn't seem to support app keypress events for menu shortcuts.
|
||||
// Will enable when https://github.com/microsoft/playwright/issues/8004 is resolved.
|
||||
test.skip('keyboard shortcuts: zoom', async () => {
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
const defaultZoom: number | undefined = await app.evaluate(
|
||||
({ BrowserWindow }) =>
|
||||
BrowserWindow.getFocusedWindow()?.webContents?.zoomFactor,
|
||||
);
|
||||
|
||||
expect(defaultZoom).toBeDefined();
|
||||
|
||||
await mainWindow.keyboard.press(`${metaOrCtrl}+Equal`);
|
||||
const postZoomIn = await app.evaluate(
|
||||
({ BrowserWindow }): number | undefined =>
|
||||
BrowserWindow.getFocusedWindow()?.webContents?.zoomFactor,
|
||||
);
|
||||
|
||||
expect(postZoomIn).toBeGreaterThan(defaultZoom as number);
|
||||
|
||||
await mainWindow.keyboard.press(`${metaOrCtrl}+0`);
|
||||
const postZoomReset = await app.evaluate(
|
||||
({ BrowserWindow }): number | undefined =>
|
||||
BrowserWindow.getFocusedWindow()?.webContents?.zoomFactor,
|
||||
);
|
||||
|
||||
expect(postZoomReset).toEqual(defaultZoom);
|
||||
|
||||
await mainWindow.keyboard.press(`${metaOrCtrl}+Minus`);
|
||||
const postZoomOut: number | undefined = await app.evaluate(
|
||||
({ BrowserWindow }) =>
|
||||
BrowserWindow.getFocusedWindow()?.webContents?.zoomFactor,
|
||||
);
|
||||
|
||||
expect(postZoomOut).toBeLessThan(defaultZoom as number);
|
||||
});
|
||||
|
||||
// Currently disabled. Playwright doesn't seem to support app keypress events for menu shortcuts.
|
||||
// Will enable when https://github.com/microsoft/playwright/issues/8004 is resolved.
|
||||
test.skip('keyboard shortcuts: back and forward', async () => {
|
||||
const mainWindow = (await spawnApp()) as Page;
|
||||
await mainWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
await Promise.all([
|
||||
mainWindow.click('#nav-products-link'),
|
||||
mainWindow.waitForNavigation({ waitUntil: 'domcontentloaded' }),
|
||||
]);
|
||||
|
||||
// Go back
|
||||
// console.log(`${metaOrAlt}+ArrowLeft`);
|
||||
await mainWindow.keyboard.press(`${metaOrAlt}+ArrowLeft`);
|
||||
await mainWindow.waitForNavigation({ waitUntil: 'domcontentloaded' });
|
||||
|
||||
const backUrl = await mainWindow.evaluate(() => window.location.href);
|
||||
|
||||
expect(backUrl).toBe(DEFAULT_CONFIG.targetUrl);
|
||||
|
||||
// Go forward
|
||||
// console.log(`${metaOrAlt}+ArrowRight`);
|
||||
await mainWindow.keyboard.press(`${metaOrAlt}+ArrowRight`);
|
||||
await mainWindow.waitForNavigation({ waitUntil: 'domcontentloaded' });
|
||||
|
||||
const forwardUrl = await mainWindow.evaluate(() => window.location.href);
|
||||
|
||||
expect(forwardUrl).not.toBe(DEFAULT_CONFIG.targetUrl);
|
||||
});
|
||||
|
||||
test('no errors thrown in console', async () => {
|
||||
await spawnApp({ ...DEFAULT_CONFIG }, false);
|
||||
const mainWindow = await app.firstWindow();
|
||||
mainWindow.addListener('console', (consoleMessage: ConsoleMessage) => {
|
||||
try {
|
||||
expect(consoleMessage.type()).not.toBe('error');
|
||||
} catch {
|
||||
// Do it this way so we'll see the whole message, not just
|
||||
// expect('error').not.toBe('error')
|
||||
// which isn't particularly useful
|
||||
expect({
|
||||
message: 'console.error called unexpectedly with',
|
||||
consoleMessage,
|
||||
}).toBeUndefined();
|
||||
}
|
||||
});
|
||||
// Give the app 5 seconds to spin up and ensure no errors happened
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
});
|
||||
|
||||
test('basic auth', async () => {
|
||||
const mainWindow = (await spawnApp({
|
||||
targetUrl: 'http://httpbin.org/basic-auth/foo/bar',
|
||||
basicAuthUsername: 'foo',
|
||||
basicAuthPassword: 'bar',
|
||||
})) as Page;
|
||||
await mainWindow.waitForLoadState('networkidle');
|
||||
|
||||
const documentText = await mainWindow.evaluate<string>(
|
||||
'document.documentElement.innerText',
|
||||
);
|
||||
|
||||
const documentJSON = JSON.parse(documentText) as {
|
||||
authenticated: boolean;
|
||||
user: string;
|
||||
};
|
||||
|
||||
expect(documentJSON).toEqual({
|
||||
authenticated: true,
|
||||
user: 'foo',
|
||||
});
|
||||
});
|
||||
|
||||
test('basic auth without pre-providing', async () => {
|
||||
const mainWindow = (await spawnApp({
|
||||
targetUrl: 'http://httpbin.org/basic-auth/foo/bar',
|
||||
})) as Page;
|
||||
await mainWindow.waitForLoadState('load');
|
||||
|
||||
// Give the app a few seconds to open the login window
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
const appWindows = app.windows();
|
||||
|
||||
expect(appWindows).toHaveLength(2);
|
||||
|
||||
const loginWindow = appWindows.filter((x) => x !== mainWindow)[0];
|
||||
|
||||
await loginWindow.waitForLoadState('domcontentloaded');
|
||||
|
||||
const usernameField = await loginWindow.$('#username-input');
|
||||
|
||||
expect(usernameField).not.toBeNull();
|
||||
|
||||
const passwordField = await loginWindow.$('#password-input');
|
||||
|
||||
expect(passwordField).not.toBeNull();
|
||||
|
||||
const submitButton = await loginWindow.$('#submit-form-button');
|
||||
|
||||
expect(submitButton).not.toBeNull();
|
||||
|
||||
await usernameField?.fill('foo');
|
||||
await passwordField?.fill('bar');
|
||||
await submitButton?.click();
|
||||
|
||||
await mainWindow.waitForLoadState('networkidle');
|
||||
|
||||
const documentText = await mainWindow.evaluate<string>(
|
||||
'document.documentElement.innerText',
|
||||
);
|
||||
|
||||
const documentJSON = JSON.parse(documentText) as {
|
||||
authenticated: boolean;
|
||||
user: string;
|
||||
};
|
||||
|
||||
expect(documentJSON).toEqual({
|
||||
authenticated: true,
|
||||
user: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createInject(filename: string, contents: string): void {
|
||||
fs.writeFileSync(path.join(INJECT_DIR, filename), contents);
|
||||
}
|
||||
|
||||
function nukeInjects(): void {
|
||||
if (!fs.existsSync(INJECT_DIR)) {
|
||||
return;
|
||||
}
|
||||
const injected = fs
|
||||
.readdirSync(INJECT_DIR)
|
||||
.filter((x) => x !== '_placeholder');
|
||||
injected.forEach((x) => fs.unlinkSync(path.join(INJECT_DIR, x)));
|
||||
}
|
||||
|
||||
function nukeLogs(logFileDir: string): void {
|
||||
const logs = fs.readdirSync(logFileDir).filter((x) => x.endsWith('.log'));
|
||||
logs.forEach((x) => fs.unlinkSync(path.join(logFileDir, x)));
|
||||
}
|
||||
|
||||
function showLogs(logFileDir: string): void {
|
||||
const logs = fs.readdirSync(logFileDir).filter((x) => x.endsWith('.log'));
|
||||
for (const logFile of logs) {
|
||||
log.log(fs.readFileSync(path.join(logFileDir, logFile)).toString());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user