diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3821bae6..3fc9ca55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,6 +36,22 @@ jobs: node-version: 20 cache: 'yarn' + # This global ffmpeg is used for screenshots (see below) + - uses: FedericoCarboni/setup-ffmpeg@v3 + with: + ffmpeg-version: release + # arm not yet supported on macos + architecture: ${{ matrix.os == 'macos-latest' && 'x64' || '' }} + # Linking type of the binaries. Use "shared" to download shared binaries and + # "static" for statically linked ones. Shared builds are currently only available + # for windows releases. Defaults to "static" + linking-type: static + # As of version 3 of this action, builds are no longer downloaded from GitHub + # except on Windows: https://github.com/GyanD/codexffmpeg/releases. + github-token: ${{ github.server_url == 'https://github.com' && github.token || '' }} + + - run: ffmpeg -version + # Because of timeout issue https://github.com/yarnpkg/yarn/issues/4890 - run: yarn install --immutable --network-timeout 1000000 @@ -95,6 +111,17 @@ jobs: APPLE_API_KEY_ID: ${{ secrets.api_key_id }} APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }} + - run: npx tsx script/e2e.mts 'dist/mac-arm64/LosslessCut.app/Contents/MacOS/LosslessCut' screenshot-macos.jpeg + if: startsWith(matrix.os, 'macos') + - run: npx tsx script/e2e.mts 'dist/win-x64/LosslessCut.app/Contents/MacOS/LosslessCut' screenshot-windows.jpeg + if: startsWith(matrix.os, 'windows') + - run: | + export DISPLAY=:0 + sudo Xvfb -ac :0 -screen 0 1280x1024x24 > /dev/null 2>&1 & + sleep 1 + npx tsx script/e2e.mts 'dist/linux-x64/LosslessCut.app/Contents/MacOS/LosslessCut' screenshot-linux.jpeg + if: startsWith(matrix.os, 'ubuntu') + - name: (MacOS) Upload to Mac App Store if: startsWith(matrix.os, 'macos') && env.is_tag == 'true' run: | @@ -108,6 +135,7 @@ jobs: path: | dist/LosslessCut-mac-arm64.dmg dist/LosslessCut-mac-x64.dmg + screenshot-macos.jpeg - name: (Windows) Upload artifacts uses: actions/upload-artifact@v3 @@ -116,6 +144,7 @@ jobs: name: Windows path: | dist/LosslessCut-win-x64.7z + screenshot-windows.jpeg - name: (Linux) Upload artifacts uses: actions/upload-artifact@v3 @@ -126,4 +155,5 @@ jobs: dist/LosslessCut-linux-arm64.tar.bz2 dist/LosslessCut-linux-armv7l.tar.bz2 dist/LosslessCut-linux-x64.tar.bz2 + screenshot-linux.jpeg diff --git a/package.json b/package.json index 59560e45..5b076fa0 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "evergreen-ui": "^6.13.1", "fast-xml-parser": "^4.4.1", "framer-motion": "^9.0.3", + "got": "^14.4.2", "i18next-parser": "^9.0.1", "icon-gen": "^4.0.0", "immer": "^10.0.2", diff --git a/script/e2e.mts b/script/e2e.mts new file mode 100644 index 00000000..0a4a1257 --- /dev/null +++ b/script/e2e.mts @@ -0,0 +1,63 @@ +/* eslint-disable no-console */ +import assert from 'node:assert'; +import { execa } from 'execa'; +import got from 'got'; +import os from 'node:os'; +import timers from 'node:timers/promises'; + + +const losslessCutExePath = process.argv[2]; +assert(losslessCutExePath); +const screenshotOutPath = process.argv[3]; +assert(screenshotOutPath); + + +const port = 8081; + +const ps = execa(losslessCutExePath, ['--http-api', String(port)]); + +console.log('Started', losslessCutExePath); + +// eslint-disable-next-line unicorn/prefer-top-level-await +ps.catch((err) => console.error(err)); + +const client = got.extend({ prefixUrl: `http://127.0.0.1:${port}`, timeout: { request: 5000 } }); + +async function captureScreenshot(outPath: string) { + // https://trac.ffmpeg.org/wiki/Capture/Desktop#Windows + + await execa('ffmpeg', [ + ...(os.platform() === 'darwin' ? ['-r', '30', '-pix_fmt', 'uyvy422', '-f', 'avfoundation', '-i', '1:none'] : []), + ...(os.platform() === 'win32' ? ['-f', 'gdigrab', '-framerate', '30', '-i', 'desktop'] : []), + ...(os.platform() === 'linux' ? ['-framerate', '25', '-f', 'x11grab', '-i', ':0.0+0,0'] : []), + '-vframes', '1', outPath, + ], { timeout: 30000 }); +} + +try { + const resp = await client('', { + retry: { backoffLimit: 5000, limit: 10 }, + hooks: { beforeRequest: [() => { console.log('attempt'); }] }, + }).text(); + assert(resp.length > 0); + + console.log('Waiting for UI to settle'); + + await timers.setTimeout(5000); + + console.log('Capturing screenshot'); + + await captureScreenshot(screenshotOutPath); + + console.log('Sending quit command'); + + await client.post('api/action/quit').text(); +} finally { + // ps.cancel(); +} + +console.log('Waiting for app to quit'); + +await ps; + +console.log('App has quit'); diff --git a/src/main/index.ts b/src/main/index.ts index d91fb374..8496bb49 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -11,6 +11,7 @@ import JSON5 from 'json5'; import remote from '@electron/remote/main'; import { stat } from 'node:fs/promises'; import assert from 'node:assert'; +import timers from 'node:timers/promises'; import logger from './logger.js'; import menu from './menu.js'; @@ -404,7 +405,9 @@ export function focusWindow() { } } -export function quitApp() { +export async function quitApp() { + // allow HTTP API to respond etc. + await timers.setTimeout(1000); electron.app.quit(); } diff --git a/yarn.lock b/yarn.lock index 879d8ff3..20630cfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1797,6 +1797,13 @@ __metadata: languageName: node linkType: hard +"@sindresorhus/is@npm:^7.0.0": + version: 7.0.0 + resolution: "@sindresorhus/is@npm:7.0.0" + checksum: 10/768f9155dba0dda0277353693babcdf81bbbbec91db2a86a1533696e8cf857e6cf66e517b2ec8922cb25a906bf2e8576c3cf556cd9148ba96451751eb4047536 + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -1806,6 +1813,15 @@ __metadata: languageName: node linkType: hard +"@szmarczak/http-timer@npm:^5.0.1": + version: 5.0.1 + resolution: "@szmarczak/http-timer@npm:5.0.1" + dependencies: + defer-to-connect: "npm:^2.0.1" + checksum: 10/fc9cb993e808806692e4a3337c90ece0ec00c89f4b67e3652a356b89730da98bc824273a6d67ca84d5f33cd85f317dcd5ce39d8cc0a2f060145a608a7cb8ce92 + languageName: node + linkType: hard + "@tokenizer/token@npm:^0.3.0": version: 0.3.0 resolution: "@tokenizer/token@npm:0.3.0" @@ -2027,6 +2043,13 @@ __metadata: languageName: node linkType: hard +"@types/http-cache-semantics@npm:^4.0.4": + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 10/a59566cff646025a5de396d6b3f44a39ab6a74f2ed8150692e0f31cc52f3661a68b04afe3166ebe0d566bd3259cb18522f46e949576d5204781cd6452b7fe0c5 + languageName: node + linkType: hard + "@types/http-errors@npm:*": version: 2.0.4 resolution: "@types/http-errors@npm:2.0.4" @@ -3347,6 +3370,28 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "cacheable-lookup@npm:7.0.0" + checksum: 10/69ea78cd9f16ad38120372e71ba98b64acecd95bbcbcdad811f857dc192bad81ace021f8def012ce19178583db8d46afd1a00b3e8c88527e978e049edbc23252 + languageName: node + linkType: hard + +"cacheable-request@npm:^12.0.1": + version: 12.0.1 + resolution: "cacheable-request@npm:12.0.1" + dependencies: + "@types/http-cache-semantics": "npm:^4.0.4" + get-stream: "npm:^9.0.1" + http-cache-semantics: "npm:^4.1.1" + keyv: "npm:^4.5.4" + mimic-response: "npm:^4.0.0" + normalize-url: "npm:^8.0.1" + responselike: "npm:^3.0.0" + checksum: 10/91ca6f3cdcbec3309032b96ba8e94e9d3978ab2e9ee048d75b32acf8a0f06c4cd4739317a39ce621469130f838b06713c1333d35b212e87633c4812d7f18b17f + languageName: node + linkType: hard + "cacheable-request@npm:^7.0.2": version: 7.0.2 resolution: "cacheable-request@npm:7.0.2" @@ -4106,7 +4151,7 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.0": +"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": version: 2.0.1 resolution: "defer-to-connect@npm:2.0.1" checksum: 10/8a9b50d2f25446c0bfefb55a48e90afd58f85b21bcf78e9207cd7b804354f6409032a1705c2491686e202e64fc05f147aa5aa45f9aa82627563f045937f5791b @@ -5709,6 +5754,13 @@ __metadata: languageName: node linkType: hard +"form-data-encoder@npm:^4.0.2": + version: 4.0.2 + resolution: "form-data-encoder@npm:4.0.2" + checksum: 10/71a00a1e954267f8b87d4301936dda7177cfe18714a92930c87ebc132acb8b19ed12e9b9f8ad47af52c1cc582fd45c1771f66f850a6d319147731160f6b9b3fa + languageName: node + linkType: hard + "form-data@npm:^4.0.0": version: 4.0.0 resolution: "form-data@npm:4.0.0" @@ -6188,6 +6240,25 @@ __metadata: languageName: node linkType: hard +"got@npm:^14.4.2": + version: 14.4.2 + resolution: "got@npm:14.4.2" + dependencies: + "@sindresorhus/is": "npm:^7.0.0" + "@szmarczak/http-timer": "npm:^5.0.1" + cacheable-lookup: "npm:^7.0.0" + cacheable-request: "npm:^12.0.1" + decompress-response: "npm:^6.0.0" + form-data-encoder: "npm:^4.0.2" + http2-wrapper: "npm:^2.2.1" + lowercase-keys: "npm:^3.0.0" + p-cancelable: "npm:^4.0.1" + responselike: "npm:^3.0.0" + type-fest: "npm:^4.19.0" + checksum: 10/4f3b37594cce3d08ce26e917637eae28a96637cd1bd6569dd6fd61f1224b597dca470aa59597431d1b2b5dbb4a98fa246766dcb7085bb8cbde22f1e762036d03 + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.8": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" @@ -6370,7 +6441,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: 10/362d5ed66b12ceb9c0a328fb31200b590ab1b02f4a254a697dc796850cc4385603e75f53ec59f768b2dad3bfa1464bd229f7de278d2899a0e3beffc634b6683f @@ -6411,6 +6482,16 @@ __metadata: languageName: node linkType: hard +"http2-wrapper@npm:^2.2.1": + version: 2.2.1 + resolution: "http2-wrapper@npm:2.2.1" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.2.0" + checksum: 10/e7a5ac6548318e83fc0399cd832cdff6bbf902b165d211cad47a56ee732922e0aa1107246dd884b12532a1c4649d27c4d44f2480911c65202e93c90bde8fa29d + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -7311,7 +7392,7 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0, keyv@npm:^4.5.3": +"keyv@npm:^4.0.0, keyv@npm:^4.5.3, keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -7565,6 +7646,7 @@ __metadata: file-type: "patch:file-type@npm%3A19.4.1#~/.yarn/patches/file-type-npm-19.4.1-d18086444c.patch" framer-motion: "npm:^9.0.3" fs-extra: "npm:^8.1.0" + got: "npm:^14.4.2" i18next: "npm:^23.12.2" i18next-fs-backend: "npm:^2.3.2" i18next-parser: "npm:^9.0.1" @@ -7642,6 +7724,13 @@ __metadata: languageName: node linkType: hard +"lowercase-keys@npm:^3.0.0": + version: 3.0.0 + resolution: "lowercase-keys@npm:3.0.0" + checksum: 10/67a3f81409af969bc0c4ca0e76cd7d16adb1e25aa1c197229587eaf8671275c8c067cd421795dbca4c81be0098e4c426a086a05e30de8a9c587b7a13c0c7ccc5 + languageName: node + linkType: hard + "lowlight@npm:^1.17.0": version: 1.20.0 resolution: "lowlight@npm:1.20.0" @@ -7866,6 +7955,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-response@npm:4.0.0" + checksum: 10/33b804cc961efe206efdb1fca6a22540decdcfce6c14eb5c0c50e5ae9022267ab22ce8f5568b1f7247ba67500fe20d523d81e0e9f009b321ccd9d472e78d1850 + languageName: node + linkType: hard + "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -8225,6 +8321,13 @@ __metadata: languageName: node linkType: hard +"normalize-url@npm:^8.0.1": + version: 8.0.1 + resolution: "normalize-url@npm:8.0.1" + checksum: 10/ae392037584fc5935b663ae4af475351930a1fc39e107956cfac44f42d5127eec2d77d9b7b12ded4696ca78103bafac5b6206a0ea8673c7bffecbe13544fcc5a + languageName: node + linkType: hard + "now-and-later@npm:^3.0.0": version: 3.0.0 resolution: "now-and-later@npm:3.0.0" @@ -8435,6 +8538,13 @@ __metadata: languageName: node linkType: hard +"p-cancelable@npm:^4.0.1": + version: 4.0.1 + resolution: "p-cancelable@npm:4.0.1" + checksum: 10/64de7b0be4c8bacc006488e0e90aa66fbcceb4da4f6fb84584573145f015f9650fe6ac26470897b3e82a3b528f6c60ea276b84cc315e35c45e9f12dec062a295 + languageName: node + linkType: hard + "p-limit@npm:^2.0.0, p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -9333,7 +9443,7 @@ __metadata: languageName: node linkType: hard -"resolve-alpn@npm:^1.0.0": +"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" checksum: 10/744e87888f0b6fa0b256ab454ca0b9c0b80808715e2ef1f3672773665c92a941f6181194e30ccae4a8cd0adbe0d955d3f133102636d2ee0cca0119fec0bc9aec @@ -9424,6 +9534,15 @@ __metadata: languageName: node linkType: hard +"responselike@npm:^3.0.0": + version: 3.0.0 + resolution: "responselike@npm:3.0.0" + dependencies: + lowercase-keys: "npm:^3.0.0" + checksum: 10/e0cc9be30df4f415d6d83cdede3c5c887cd4a73e7cc1708bcaab1d50a28d15acb68460ac5b02bcc55a42f3d493729c8856427dcf6e57e6e128ad05cba4cfb95e + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -10863,6 +10982,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.19.0": + version: 4.26.0 + resolution: "type-fest@npm:4.26.0" + checksum: 10/f5fe86d2c3db693f7154c8ab0d228a89394e4c446f2ed30ea3b61afaea9757c87c4e79475ef8d6f5fafbd7a4efd302e3b0237d9657dd425228f20a27feee3aef + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18"