diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 098bcdc0..a592ab49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,22 @@ name: CI on: + workflow_dispatch: push: branches: [master] pull_request: branches: [master] +concurrency: + group: ${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + jobs: examples: runs-on: ubuntu-latest name: Test ${{ matrix.project }} strategy: + fail-fast: false matrix: project: - browser-add-readable-stream @@ -49,28 +55,32 @@ jobs: run: working-directory: examples/${{ matrix.project }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: lts/* - - run: npm install - - uses: GabrielBB/xvfb-action@v1 - name: Run tests - with: - run: npm test - working-directory: examples/${{ matrix.project }} + - name: Install dependencies + run: npm install + - name: Install Playwright + run: npx -y playwright install --with-deps + - name: Run tests + run: npm run test + env: + CI: true monorepo: runs-on: ubuntu-latest name: Test monorepo steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: lts/* - name: Install dependencies run: npm install - - uses: GabrielBB/xvfb-action@v1 - name: Run test:examples - with: - run: npm run test:examples + - name: Install Playwright + run: npx -y playwright install --with-deps + - name: Run test:examples + run: npm run test:examples + env: + CI: true diff --git a/No-real-changes.tmp b/No-real-changes.tmp new file mode 100644 index 00000000..e69de29b diff --git a/examples/browser-service-worker/tests/test.js b/examples/browser-service-worker/tests/test.js index 805a865a..aeff782f 100644 --- a/examples/browser-service-worker/tests/test.js +++ b/examples/browser-service-worker/tests/test.js @@ -1,3 +1,4 @@ +// @ts-check import { test, expect } from '@playwright/test'; import { playwright } from 'test-util-ipfs-example'; @@ -10,30 +11,57 @@ play.describe('browser service worker:', () => { // DOM const linkDOM = "a" const textDOM = "body" - const debugDOM = "#debug" play.beforeEach(async ({servers, page}) => { await page.goto(`http://localhost:${servers[0].port}/`); }) - play('should properly load the content of an IPFS hash', async ({ page }) => { - await page.waitForSelector(textDOM) - await page.waitForSelector(linkDOM) + play('should properly load the content of an IPFS hash', async ({ servers, page, context }) => { + page.on('console', msg => console.log('PAGE LOG:', msg.text())); + page.on("pageerror", (err) => { + console.trace(`pageerror: ${err.message}`) + }) expect(await page.textContent(textDOM)).toContain("Load content by adding IPFS path to the URL") expect(await page.textContent(linkDOM)).toContain("/ipfs/bafy") - await page.waitForSelector(`${debugDOM}:has-text("SW is ready")`, { - state: 'attached' - }) + /** + * Request /view path directly, as this is still handled by the service worker but doesn't break tests in github CI. + * @see https://github.com/ipfs-examples/js-ipfs-examples/blob/master/examples/browser-service-worker/src/service.js#L48-L52 + * @see https://github.com/ipfs-examples/js-ipfs-examples/pull/527 + */ + const ipfsRequestUrl = `http://localhost:${servers[0].port}/view/ipfs/Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD` - const currentURL = await page.url(); - await page.goto(`${currentURL}ipfs/Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD`); - await page.waitForSelector(textDOM) + /** + * Wait for the service worker to be ready + * @see https://playwright.dev/docs/service-workers-experimental#accessing-service-workers-and-waiting-for-activation + */ + await page.evaluate(async () => { + const registration = await window.navigator.serviceWorker.getRegistration(); + if (registration?.active?.state === 'activated') { + console.log('Service worker is already activated') + return; + } + await /** @type {Promise} */(new Promise(res => { + window.navigator.serviceWorker.addEventListener('controllerchange', () => { + console.log('Service worker is activated') + res() + }) + })); + }); + + const serviceWorkerResponsePromise = new Promise((resolve) => { + context.on('response', async (response) => { + if (response.url() === ipfsRequestUrl && response.fromServiceWorker()) { + resolve(response); + } + }) + }) - const elementFrame = await page.waitForSelector("iframe") - const frame = await elementFrame.contentFrame() + await page.goto(ipfsRequestUrl, {waitUntil: 'commit'}); + const serviceWorkerResponse = await serviceWorkerResponsePromise - expect(await frame.textContent('pre')).toContain("hello world") + expect(await serviceWorkerResponse.status()).toBe(200) + expect(await serviceWorkerResponse.text()).toContain("hello world") }); }); diff --git a/examples/run-in-electron/package.json b/examples/run-in-electron/package.json index a44be929..e56aca9f 100644 --- a/examples/run-in-electron/package.json +++ b/examples/run-in-electron/package.json @@ -15,7 +15,7 @@ "clean": "echo 'Nothing to clean...'", "start": "electron .", "serve": "npm run start", - "test": "node test.mjs" + "test": "xvfb-maybe node test.mjs" }, "dependencies": { "ipfs-core": "^0.16.0" @@ -23,7 +23,8 @@ "devDependencies": { "electron": "^20.1.1", "electron-rebuild": "^3.1.1", - "test-util-ipfs-example": "^1.0.2" + "test-util-ipfs-example": "^1.0.2", + "xvfb-maybe": "^0.2.1" }, "greenkeeper": { "ignore": [ diff --git a/lib/test-util-ipfs-example/playwright/servers.js b/lib/test-util-ipfs-example/playwright/servers.js index 55e89c9f..b31c5936 100644 --- a/lib/test-util-ipfs-example/playwright/servers.js +++ b/lib/test-util-ipfs-example/playwright/servers.js @@ -2,12 +2,19 @@ import sirv from 'sirv' import polka from 'polka' import stoppable from 'stoppable' -const servers = (serverConfiguration = []) => { +/** + * @template T + * @template W + * @template PT + * @template PW + * @return {import('@playwright/test').Fixtures} + */ +const servers = (serverConfiguration = [], explicitStop = false) => { return { // We pass a tuple to specify fixtures options. // In this case, we mark this fixture as worker-scoped. servers: [ async ({}, use, workerInfo) => { - let promiseServers = []; + const promiseServers = []; const servers = []; const configurations = [...serverConfiguration] @@ -32,35 +39,41 @@ const servers = (serverConfiguration = []) => { const app = polka(); - promiseServers.push(new Promise((resolve, reject) => { + promiseServers.push(/** @type {Promise} */(new Promise((resolve, reject) => { app .use(staticFiles) .listen(port, err => { if (err) throw err; + const server = stoppable(app.server) servers.push({ - server: stoppable(app.server), - port: port + server, + port: port, + stop: async () => { + console.log(`Stopping server on port ${port}...`) + await new Promise(r => server.stop(r)) + console.log(`Server on port ${port} stopped`) + } }) console.log(`> Ready on localhost:${port}!`); resolve(); }); - })) + }))) } await Promise.all(promiseServers) // Use the server in the tests. - await use(servers); + await use(servers) + if (!explicitStop) { + // Cleanup. + console.log('Stopping servers...'); - // Cleanup. - console.log('Stopping servers...'); - promiseServers = servers.map(m => new Promise(f => m.server.close(f))); - - await Promise.all(promiseServers) - console.log('Servers stopped'); + await Promise.all(servers.map(s => s.stop())) + console.log('Servers stopped'); + } }, { scope: 'worker', auto: true } ], } }