From 106627d992fb55c75c2461a18341ca3058c77110 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 14 Jun 2023 00:33:21 -0700 Subject: [PATCH] fix: resolve e2e test failures (#2122) * feat: resolve e2e test failures * fix: re-add get-port * chore: remove http-serve and update http-server * test(fix): explore.page.js race condition * fix: run e2e tests 10 times on CI to ensure no flakiness * chore: move repeat-each to github CI this ensures kubo interop CI test only runs them once.. we don't want the burden of flakiness on them * test(chore): make remote-api.test.js test in serial for now * test(fix): remote-api.test.js * test(ci): allow multiple workers in CI * Revert "test(ci): allow multiple workers in CI" This reverts commit ad43e04d914dbe94e8dc396877a84f267218e116. * test(CI): shard e2e playwright tests in CI --- .github/workflows/test-e2e.yml | 6 +- package-lock.json | 192 ++++++++++++++++++++++----------- package.json | 4 +- test/e2e/explore.test.js | 7 +- test/e2e/ipns.test.js | 9 +- test/e2e/playwright.config.js | 8 +- test/e2e/remote-api.test.js | 8 +- test/e2e/setup/global-setup.js | 27 +++++ test/e2e/setup/ipfs-backend.js | 11 +- 9 files changed, 182 insertions(+), 90 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index c18f64ed5..4e1be7961 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -9,7 +9,9 @@ jobs: strategy: fail-fast: false matrix: - backend: [go] # TODO: add 'js' – see https://github.com/ipfs/ipfs-webui/issues/1737 + backend: [go] + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + shardTotal: [10] steps: - uses: actions/checkout@v3.5.2 @@ -39,7 +41,7 @@ jobs: run: npm run test:build - name: Run E2E against ${{ matrix.backend }}-ipfs - run: E2E_IPFSD_TYPE=${{ matrix.backend }} npm run test:e2e + run: E2E_IPFSD_TYPE=${{ matrix.backend }} npm run test:e2e -- --repeat-each 10 --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} # run each test 10 times to ensure no flakiness - name: Generate nyc coverage report id: coverage diff --git a/package-lock.json b/package-lock.json index 17b0a98a3..34a261d18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -145,9 +145,9 @@ "esm": "^3.2.25", "fake-indexeddb": "^3.1.8", "get-port": "^5.1.1", - "go-ipfs": "^0.18.1", + "go-ipfs": "^0.20.0", "http-proxy": "^1.18.1", - "http-server": "^0.12.3", + "http-server": "^14.1.1", "ipfs": "0.58.3", "ipfs-core-utils": "^0.18.0", "ipfsd-ctl": "^12.2.2", @@ -23625,15 +23625,6 @@ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -27243,40 +27234,6 @@ "dev": true, "optional": true }, - "node_modules/ecstatic": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", - "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", - "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", - "dev": true, - "dependencies": { - "he": "^1.1.1", - "mime": "^1.6.0", - "minimist": "^1.1.0", - "url-join": "^2.0.5" - }, - "bin": { - "ecstatic": "lib/ecstatic.js" - } - }, - "node_modules/ecstatic/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ecstatic/node_modules/url-join": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha512-c2H1fIgpUdwFRIru9HFno5DT73Ok8hg5oOb5AT3ayIgvCRfxgs2jyt5Slw8kEB7j3QUr6yJmMPDT/odjk7jXow==", - "dev": true - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -31884,9 +31841,9 @@ } }, "node_modules/go-ipfs": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/go-ipfs/-/go-ipfs-0.18.1.tgz", - "integrity": "sha512-hXfjQRqet/H8mTSQVKiuTSMrvjv8cAGQMHbr12DHAHGsSMS9IuGCOntkVEhnNOnmP/WXcrxRVxLu6xz/mPLlZg==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/go-ipfs/-/go-ipfs-0.20.0.tgz", + "integrity": "sha512-tQNehjHlaPoG6V3Fv5dPnRkOsAvDFYBcdCRPsK/MtOMIxuvZk7XOotuRq7nFS+iSkECpopAjFCFHa7QZRq+REQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -33538,37 +33495,142 @@ } }, "node_modules/http-server": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", - "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", "dev": true, "dependencies": { - "basic-auth": "^1.0.3", - "colors": "^1.4.0", + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", "corser": "^2.0.1", - "ecstatic": "^3.3.2", - "http-proxy": "^1.18.0", - "minimist": "^1.2.5", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", "opener": "^1.5.1", - "portfinder": "^1.0.25", + "portfinder": "^1.0.28", "secure-compare": "3.0.1", - "union": "~0.5.0" + "union": "~0.5.0", + "url-join": "^4.0.1" }, "bin": { - "hs": "bin/http-server", "http-server": "bin/http-server" }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/http-server/node_modules/basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha512-CtGuTyWf3ig+sgRyC7uP6DM3N+5ur/p8L+FPfsd+BbIfIs74TFfCajZTHnCw6K5dqM0bZEbRIqRy1fAdiUJhTA==", + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" } }, "node_modules/http-signature": { diff --git a/package.json b/package.json index 94f213ef3..3dce27832 100644 --- a/package.json +++ b/package.json @@ -176,9 +176,9 @@ "esm": "^3.2.25", "fake-indexeddb": "^3.1.8", "get-port": "^5.1.1", - "go-ipfs": "^0.18.1", + "go-ipfs": "^0.20.0", "http-proxy": "^1.18.1", - "http-server": "^0.12.3", + "http-server": "^14.1.1", "ipfs": "0.58.3", "ipfs-core-utils": "^0.18.0", "ipfsd-ctl": "^12.2.2", diff --git a/test/e2e/explore.test.js b/test/e2e/explore.test.js index 59a3288d7..46ac948e7 100644 --- a/test/e2e/explore.test.js +++ b/test/e2e/explore.test.js @@ -46,12 +46,7 @@ async function testExploredCid ({ cid, type, humanReadableCID, page, fillOutForm await page.press('[data-id="FilesExploreForm"] button[title="Inspect"]', 'Enter') } - // wait for loading - const spinner = page.locator('.la-ball-triangle-path') - await spinner.waitFor({ state: 'hidden' }) - - // expect node type - await page.waitForSelector(`"${cid}"`) + await page.waitForSelector(`.joyride-explorer-cid [title="${cid}"]`) // cid is displayed in the CID INFO section. await page.waitForSelector(`[title="${type}"]`) if (humanReadableCID != null) { diff --git a/test/e2e/ipns.test.js b/test/e2e/ipns.test.js index 9e0256de8..2967d0ac6 100644 --- a/test/e2e/ipns.test.js +++ b/test/e2e/ipns.test.js @@ -3,6 +3,9 @@ import { createController } from 'ipfsd-ctl' import { path as getGoIpfsPath } from 'go-ipfs' import * as kuboRpcModule from 'kubo-rpc-client' +// TODO: Fix parallelism of these tests +test.describe.configure({ mode: 'serial' }) + test.describe('IPNS publishing', () => { let ipfsd let peeraddr @@ -72,12 +75,16 @@ test.describe('IPNS publishing', () => { let keyName let ipfs test.beforeEach(async ({ page }) => { - keyName = 'pet-name-e2e-ipns-test-' + new Date().getTime() + keyName = 'pet-name-e2e-ipns-test-' + new Date().getTime() + Math.random().toString(16).slice(2) ipfs = kuboRpcModule.create(process.env.IPFS_RPC_ADDR) await ipfs.key.gen(keyName) await page.goto('/#/files') await page.reload() }) + test.afterEach(async () => { + await ipfs.key.rm(keyName) + ipfs = null + }) const testFilename = 'ipns-test.txt' const testCid = '/ipfs/bafyaaeqkcaeaeeqknfyg44znorsxg5akdafa' diff --git a/test/e2e/playwright.config.js b/test/e2e/playwright.config.js index 9bedac235..e0aba486f 100644 --- a/test/e2e/playwright.config.js +++ b/test/e2e/playwright.config.js @@ -19,7 +19,6 @@ const config = { viewport: { width: 1366, height: 768 }, baseURL: `http://localhost:${webuiPort}/`, storageState: 'test/e2e/state.json', - // actionTimeout: 30* 1000, trace: 'on-first-retry' }, /* TODO: test against other engines? @@ -50,14 +49,15 @@ const config = { webServer: [ { command: `node ipfs-backend.js ${rpcPort}`, - url: `http://127.0.0.1:${rpcPort}/webui`, // we don't use webui bundled with daemon, but it is a good test of when its http server is fully booted + timeout: 5 * 1000, + port: rpcPort, cwd: './setup', reuseExistingServer: !process.env.CI }, { - // command: 'npm run start', command: `npx http-server ./build/ -c-1 -a 127.0.0.1 -p ${webuiPort}`, - port: webuiPort, + timeout: 5 * 1000, + url: `http://localhost:${webuiPort}/`, cwd: '../../', reuseExistingServer: !process.env.CI, env: { diff --git a/test/e2e/remote-api.test.js b/test/e2e/remote-api.test.js index 52c40677b..0ec08d4fe 100644 --- a/test/e2e/remote-api.test.js +++ b/test/e2e/remote-api.test.js @@ -115,11 +115,11 @@ test.describe('Remote API tests', () => { } const switchIpfsApiEndpointViaSettings = async (endpoint, page) => { - await page.click('a[href="#/settings"]') + await page.click('[role="menubar"] a[href="#/settings"]') const selector = 'input[id="api-address"]' - await page.waitForSelector(selector) - await page.fill(selector, endpoint) - await page.press(selector, 'Enter') + const locator = await page.locator(selector) + await locator.fill(endpoint) + await locator.press('Enter') await waitForIpfsApiEndpoint(endpoint, page) } diff --git a/test/e2e/setup/global-setup.js b/test/e2e/setup/global-setup.js index eb3735435..d351c3b90 100644 --- a/test/e2e/setup/global-setup.js +++ b/test/e2e/setup/global-setup.js @@ -6,6 +6,31 @@ import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) +// make sure that ipfs-backend is fully running +const ensureKuboDaemon = async (apiOpts) => { + const backendEndpoint = `${apiOpts.protocol}://${apiOpts.host}:${apiOpts.port}` + const body = new FormData() + body.append('file', new Blob(new Uint8Array([1, 2, 3])), 'test.txt') + + const fakeFileResult = await fetch(`${backendEndpoint}/api/v0/block/put`, { + body, + method: 'POST' + }) + if (!fakeFileResult.ok) { + console.error('fakeFileResult not okay', await fakeFileResult.text()) + throw new Error(`IPFS backend not running at ${backendEndpoint}`) + } + + const { Key: cidString } = await fakeFileResult.json() + const getContentResult = await fetch(`${backendEndpoint}/api/v0/block/get?arg=${cidString}`, { + method: 'POST' + }) + if (!getContentResult.ok) { + console.error('Could not get fake file', await getContentResult.text()) + throw new Error(`IPFS backend not running at ${backendEndpoint}`) + } +} + const globalSetup = async config => { // Read and expose backend info in env availables inside of test() blocks const { rpcAddr, id, agentVersion, apiOpts } = JSON.parse(fs.readFileSync(path.join(__dirname, 'ipfs-backend.json'))) @@ -13,6 +38,8 @@ const globalSetup = async config => { process.env.IPFS_RPC_ID = id process.env.IPFS_RPC_VERSION = agentVersion + await ensureKuboDaemon(apiOpts) + // Set and save RPC API endpoint in storageState, so test start against // desired endpoint const { baseURL, storageState } = config.projects[0].use diff --git a/test/e2e/setup/ipfs-backend.js b/test/e2e/setup/ipfs-backend.js index 302d092ad..5fcf09028 100644 --- a/test/e2e/setup/ipfs-backend.js +++ b/test/e2e/setup/ipfs-backend.js @@ -9,10 +9,13 @@ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const { console } = windowOrGlobal +let ipfsd +let ipfs async function run (rpcPort) { + if (ipfsd != null && ipfs != null) { + throw new Error('IPFS backend already running') + } const endpoint = process.env.E2E_API_URL - let ipfsd - let ipfs if (endpoint) { // create http rpc client for endpoint passed via E2E_API_URL= ipfs = kuboRpcModule.create(endpoint) @@ -21,7 +24,6 @@ async function run (rpcPort) { const type = process.env.E2E_IPFSD_TYPE || 'go' const factory = Ctl.createFactory({ kuboRpcModule, - ipfsModule: await import('ipfs'), type, ipfsOptions: { config: { @@ -36,9 +38,6 @@ async function run (rpcPort) { { go: { ipfsBin: process.env.IPFS_GO_EXEC || (await import('go-ipfs')).default.path() - }, - js: { - ipfsBin: process.env.IPFS_JS_EXEC || path.resolve(__dirname, '../../../node_modules/ipfs/src/cli.js') } })