From b0f26ac4eb7a3131b225b4d5dd131b376254340c Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Tue, 26 Mar 2024 14:18:57 -0700 Subject: [PATCH] test: e2e test and fixture overhaul (#159) * test: load kubo fixtures and byte range tests on them chore: single kubo gateway for parallel tests chore: attempting to remove beforeEach for byte-ranges chore: working on dx for e2e chore: move gateway conformance to global setup/teardown test: e2e fixtures for subdomain and path routing chore: remove unnecessary service worker wait * chore: leftover comment cleanup * chore: remove unused dev-dep * chore: add gateway-conformance fixtures * chore: attempt to resolve loading fixtures in CI * chore: test removing fixtures --- .gitignore | 2 + package-lock.json | 487 +++++++++++++++++++--- package.json | 3 +- playwright.config.js | 10 +- src/components/collapsible.tsx | 2 +- src/components/config.tsx | 4 +- test-e2e/byte-range.test.ts | 33 +- test-e2e/fixtures/config-test-fixtures.ts | 79 ++++ test-e2e/fixtures/create-kubo-node.ts | 19 +- test-e2e/fixtures/do-range-request.ts | 39 ++ test-e2e/fixtures/load-fixture-data.ts | 9 - test-e2e/fixtures/load-kubo-fixtures.ts | 127 ++++++ test-e2e/fixtures/locators.ts | 16 +- test-e2e/fixtures/set-sw-config.ts | 59 ++- test-e2e/global-setup.js | 17 - test-e2e/global-setup.ts | 13 + test-e2e/global-teardown.ts | 7 + test-e2e/path-routing.test.ts | 7 +- test-e2e/subdomain-detection.test.ts | 30 +- test-e2e/website-loading.test.ts | 34 +- 20 files changed, 823 insertions(+), 174 deletions(-) create mode 100644 test-e2e/fixtures/config-test-fixtures.ts create mode 100644 test-e2e/fixtures/do-range-request.ts delete mode 100644 test-e2e/fixtures/load-fixture-data.ts create mode 100644 test-e2e/fixtures/load-kubo-fixtures.ts delete mode 100644 test-e2e/global-setup.js create mode 100644 test-e2e/global-setup.ts create mode 100644 test-e2e/global-teardown.ts diff --git a/.gitignore b/.gitignore index 45cdd944..ffd04799 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,5 @@ playwright-report .direnv *.nix .coverage +test-e2e/fixtures/data/test-repo +test-e2e/fixtures/data/gateway-conformance-fixtures diff --git a/package-lock.json b/package-lock.json index 6922ffbd..79adef18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,10 +32,11 @@ "copyfiles": "^2.4.1", "css-loader": "^6.10.0", "eslint-config-standard-with-typescript": "^34.0.1", + "execa": "^8.0.1", + "glob": "^10.3.10", "html-webpack-plugin": "^5.6.0", "http-proxy": "^1.18.1", "ipfsd-ctl": "^13.0.0", - "it-drain": "^3.0.5", "kubo": "^0.27.0", "kubo-rpc-client": "^3.0.4", "mini-css-extract-plugin": "^2.8.1", @@ -8246,6 +8247,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/copyfiles/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/copyfiles/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -9138,6 +9159,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/del/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/del/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/del/node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -12037,6 +12100,48 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -12350,6 +12455,16 @@ "node": ">=0.10.0" } }, + "node_modules/gh-pages/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/gh-pages/node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -12380,6 +12495,26 @@ "node": ">=8" } }, + "node_modules/gh-pages/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gh-pages/node_modules/globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", @@ -12423,6 +12558,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gh-pages/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/gh-pages/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -12495,20 +12642,22 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12532,28 +12681,6 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -15021,6 +15148,48 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/istanbul-lib-processinfo/node_modules/p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -21890,6 +22059,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/nyc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nyc/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -21977,6 +22156,26 @@ "node": ">=8.0.0" } }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/nyc/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -22013,6 +22212,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nyc/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -22975,6 +23186,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/patch-package/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/patch-package/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -23024,6 +23245,26 @@ "node": ">=10" } }, + "node_modules/patch-package/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/patch-package/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -23045,6 +23286,18 @@ "node": ">=10" } }, + "node_modules/patch-package/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/patch-package/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -23564,6 +23817,16 @@ "node": ">=12" } }, + "node_modules/playwright-test/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/playwright-test/node_modules/c8": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/c8/-/c8-8.0.1.tgz", @@ -23653,6 +23916,26 @@ "node": ">=8.0.0" } }, + "node_modules/playwright-test/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/playwright-test/node_modules/globby": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", @@ -23673,6 +23956,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright-test/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/playwright-test/node_modules/path-type": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", @@ -24418,6 +24713,16 @@ "readable-stream": "^3.4.0" } }, + "node_modules/react-native-test-runner/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/react-native-test-runner/node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -24550,6 +24855,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react-native-test-runner/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/react-native-test-runner/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -24696,6 +25021,18 @@ "node": ">=6" } }, + "node_modules/react-native-test-runner/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/react-native-test-runner/node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -25674,28 +26011,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -26861,6 +27176,16 @@ "node": ">=8" } }, + "node_modules/spawn-wrap/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/spawn-wrap/node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -26874,6 +27199,26 @@ "node": ">=8.0.0" } }, + "node_modules/spawn-wrap/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/spawn-wrap/node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -26889,6 +27234,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/spawn-wrap/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/spawn-wrap/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -27699,6 +28056,26 @@ "concat-map": "0.0.1" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/test-exclude/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index 60ccecc3..913a7d8a 100644 --- a/package.json +++ b/package.json @@ -62,10 +62,11 @@ "copyfiles": "^2.4.1", "css-loader": "^6.10.0", "eslint-config-standard-with-typescript": "^34.0.1", + "execa": "^8.0.1", + "glob": "^10.3.10", "html-webpack-plugin": "^5.6.0", "http-proxy": "^1.18.1", "ipfsd-ctl": "^13.0.0", - "it-drain": "^3.0.5", "kubo": "^0.27.0", "kubo-rpc-client": "^3.0.4", "mini-css-extract-plugin": "^2.8.1", diff --git a/playwright.config.js b/playwright.config.js index bdaab8b8..10374885 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -9,11 +9,7 @@ export default defineConfig({ forbidOnly: Boolean(process.env.CI), /* Retry on CI only */ retries: (process.env.CI != null) ? 2 : 0, - /** - * Opt out of parallel tests by setting workers to 1. - * We don't want to bombard Helia gateway with parallel requests, it's not ready for that yet. - */ - workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ // reporter: 'html', // Uncomment to generate HTML report use: { @@ -28,7 +24,9 @@ export default defineConfig({ ignoreHTTPSErrors: true }, - globalSetup: './test-e2e/global-setup.js', + + globalSetup: './test-e2e/global-setup.ts', + globalTeardown: './test-e2e/global-teardown.ts', projects: [ { diff --git a/src/components/collapsible.tsx b/src/components/collapsible.tsx index 6e75d243..72203ca3 100644 --- a/src/components/collapsible.tsx +++ b/src/components/collapsible.tsx @@ -14,7 +14,7 @@ export function Collapsible ({ children, collapsedLabel, expandedLabel, collapse return ( { setCollapsed(!isCollapsed) }} /> - +
{children}
diff --git a/src/components/config.tsx b/src/components/config.tsx index be18bfca..9f6d96d2 100644 --- a/src/components/config.tsx +++ b/src/components/config.tsx @@ -89,8 +89,8 @@ export default (): JSX.Element | null => { return (
- - + + { void saveConfig() }} /> diff --git a/test-e2e/byte-range.test.ts b/test-e2e/byte-range.test.ts index 144221b6..2775a6eb 100644 --- a/test-e2e/byte-range.test.ts +++ b/test-e2e/byte-range.test.ts @@ -1,20 +1,29 @@ -import { test, expect } from '@playwright/test' -import { waitForServiceWorker } from './fixtures/wait-for-service-worker.js' +import { testPathRouting as test, expect } from './fixtures/config-test-fixtures.js' +import { doRangeRequest } from './fixtures/do-range-request.js' test.describe('byte-ranges', () => { test('should be able to get a single character', async ({ page }) => { - await page.goto('/', { waitUntil: 'networkidle' }) - // wait for service worker to load - await waitForServiceWorker(page) + const { text, byteSize, statusCode } = await doRangeRequest({ page, range: 'bytes=1-2', path: '/ipfs/bafkqaddimvwgy3zao5xxe3debi' }) - const partialText = await page.evaluate(async () => { - const response = await fetch('/ipfs/bafkqaddimvwgy3zao5xxe3debi', { headers: { range: 'bytes=1-2' } }) - const text = await response.text() - return text - }) + expect(statusCode).toBe(206) + expect(byteSize).toBe(2) + expect(text).toBe('el') + }) + + test('can get 0-0 byte range from car with missing data', async ({ page, request }) => { + const { text, byteSize, statusCode } = await doRangeRequest({ page, range: 'bytes=0-0', path: '/ipfs/QmYhmPjhFjYFyaoiuNzYv8WGavpSRDwdHWe5B4M5du5Rtk' }) + expect(statusCode).toBe(206) + expect(byteSize).toBe(1) + expect(text).toBe('+') + }) - if (partialText == null) throw new Error('missing response') + test('can get trailing byte range from car with missing data', async ({ page }) => { + const { bytes, byteSize, statusCode } = await doRangeRequest({ page, range: 'bytes=2200-', path: '/ipfs/QmYhmPjhFjYFyaoiuNzYv8WGavpSRDwdHWe5B4M5du5Rtk' }) + // TODO: do we need to check the full 872 bytes...? + const tailBytes = [254, 0, 186, 192, 51, 66, 190, 27, 53, 147, 195, 115, 213, 65, 50, 246, 231, 155, 151, 106, 247, 199, 27, 193, 30, 214, 167, 87, 207, 246, 215, 109, 7, 72, 10, 217, 255, 62, 162, 153, 179, 12, 120, 75, 156, 74, 249, 212, 63, 218, 127, 121, 88, 111, 51, 172, 189, 176, 104, 4, 120, 182, 106, 44, 86, 33, 15, 120, 106, 126, 239, 188, 14, 190, 138, 125, 146, 14, 169, 101, 236, 250, 12, 210, 47, 145, 81, 104, 102, 153, 36, 245, 127, 60, 229, 121, 91, 204, 159, 235, 148, 44, 156, 193, 4, 59, 49, 124, 43, 30, 173, 26, 189, 95, 48, 35, 48, 91, 178, 43, 176, 171, 211, 145, 160, 251, 124, 201, 201, 29, 94, 70, 105, 216, 83, 99, 107, 86, 53, 157, 254, 16, 141, 147, 175, 2, 180, 137, 55, 174, 125, 172, 217, 214, 114, 46, 220, 23, 45, 81, 204, 215, 51, 114, 7, 115, 223, 226, 73, 114, 105, 6, 208, 213, 74, 116, 24, 98, 243, 201, 254, 195, 40, 227, 127, 14, 158, 125, 162, 150, 25, 15, 68, 101, 217, 162, 37, 253, 252, 79, 13, 33, 115, 57, 136, 0, 222, 45, 45, 105, 30, 245, 189, 133, 13, 14, 123, 15, 232, 237, 37, 8, 84, 212, 21, 233, 46, 136, 38, 236, 239, 216, 186, 175, 188, 165, 168, 69, 223, 159, 33, 96, 240, 68, 134, 121, 122, 4, 125, 16, 190, 105, 139, 78, 40, 86, 4, 125, 198, 224, 86, 198, 20, 47, 12, 207, 170, 127, 227, 92, 152, 37, 117, 137, 86, 85, 56, 67, 118, 157, 45, 31, 217, 81, 207, 129, 195, 28, 10, 238, 91, 142, 208, 116, 37, 28, 140, 161, 212, 45, 10, 208, 12, 37, 102, 165, 5, 65, 36, 153, 160, 100, 252, 115, 39, 47, 99, 24, 70, 90, 36, 190, 138, 186, 156, 30, 216, 238, 168, 207, 6, 28, 224, 108, 8, 249, 18, 143, 177, 198, 200, 189, 184, 33, 139, 249, 40, 56, 173, 235, 245, 84, 66, 123, 133, 195, 118, 145, 168, 2, 36, 118, 243, 195, 128, 234, 100, 105, 180, 141, 195, 9, 31, 204, 33, 83, 245, 138, 93, 20, 136, 151, 153, 188, 92, 65, 204, 254, 187, 69, 122, 26, 147, 86, 141, 41, 160, 75, 15, 136, 44, 186, 129, 176, 23, 87, 108, 217, 91, 195, 156, 7, 222, 74, 109, 209, 226, 15, 0, 190, 80, 194, 209, 51, 76, 5, 94, 95, 40, 206, 124, 251, 139, 162, 142, 142, 180, 4, 30, 213, 5, 44, 156, 227, 233, 80, 224, 74, 225, 6, 72, 129, 38, 11, 104, 166, 184, 225, 174, 152, 76, 206, 117, 64, 158, 252, 221, 11, 148, 24, 250, 171, 89, 117, 252, 126, 95, 169, 74, 133, 20, 180, 160, 209, 104, 31, 220, 179, 238, 33, 85, 234, 190, 30, 149, 15, 190, 57, 248, 134, 57, 26, 176, 175, 237, 133, 238, 151, 27, 135, 111, 167, 217, 12, 149, 173, 36, 34, 102, 50, 197, 17, 209, 164, 15, 61, 182, 195, 48, 56, 112, 143, 91, 210, 122, 240, 191, 144, 53, 246, 164, 169, 21, 119, 94, 235, 249, 131, 231, 162, 226, 61, 23, 81, 203, 253, 120, 160, 106, 41, 22, 70, 11, 149, 140, 231, 53, 149, 91, 197, 118, 210, 133, 206, 232, 188, 103, 61, 130, 28, 158, 104, 210, 20, 239, 143, 47, 30, 51, 127, 7, 23, 123, 55, 90, 133, 81, 40, 235, 70, 247, 0, 217, 174, 10, 190, 210, 104, 45, 121, 8, 201, 246, 180, 74, 210, 59, 145, 181, 196, 243, 192, 243, 62, 17, 57, 30, 215, 171, 186, 1, 193, 143, 94, 79, 215, 170, 79, 8, 38, 138, 138, 107, 145, 58, 122, 165, 144, 92, 179, 109, 115, 111, 122, 125, 7, 128, 112, 215, 115, 138, 93, 77, 163, 11, 144, 235, 41, 78, 22, 130, 204, 25, 33, 6, 236, 142, 250, 25, 20, 241, 220, 244, 90, 54, 149, 18, 5, 179, 175, 77, 130, 156, 97, 197, 68, 146, 235, 129, 53, 39, 33, 33, 156, 91, 176, 239, 247, 142, 203, 102, 10, 24, 227, 204, 20, 3, 55, 126, 73, 249, 152, 220, 163, 3, 251, 48, 251, 209, 182, 194, 65, 44, 40, 201, 27, 67, 64, 174, 252, 115, 139, 85, 181, 161, 214, 88, 103, 222, 188, 17, 198, 112, 123, 59, 138, 107, 173, 51, 212, 154, 189, 35, 214, 151, 82, 15, 26, 198, 39, 88, 241, 57, 58, 77, 113, 9, 74, 133, 101, 197, 12, 24, 121, 159, 87, 46, 44, 165, 83, 148, 13, 152, 175, 255, 240, 49, 110, 36, 124, 211, 219, 86, 232, 198, 153, 118, 221, 175, 139, 215, 45, 217, 101, 246, 101, 34, 154, 41, 179, 113, 48, 131, 146, 31, 226, 215, 119, 65, 97, 35, 136, 72, 179, 2, 146, 187, 120, 175, 229, 11, 143, 132, 151, 217, 134, 136, 54, 5, 211, 242, 70, 250, 210, 238, 151, 68, 63, 195, 112, 59, 179, 213, 163, 35, 28, 226, 35, 160, 44, 195, 31] - expect(partialText).toBe('el') + expect(statusCode).toBe(206) + expect(byteSize).toBe(872) + expect(bytes).toStrictEqual(tailBytes) }) }) diff --git a/test-e2e/fixtures/config-test-fixtures.ts b/test-e2e/fixtures/config-test-fixtures.ts new file mode 100644 index 00000000..e1c862c7 --- /dev/null +++ b/test-e2e/fixtures/config-test-fixtures.ts @@ -0,0 +1,79 @@ +import { test as base, type Page } from '@playwright/test' +import { setConfig, setSubdomainConfig } from './set-sw-config.js' +import { waitForServiceWorker } from './wait-for-service-worker.js' + +/** + * You should use this fixture instead of the `test` fixture from `@playwright/test` when testing path routing via the service worker. + */ +export const testPathRouting = base.extend({ + page: async ({ page }, use) => { + await page.goto('http://127.0.0.1:3333', { waitUntil: 'networkidle' }) + await waitForServiceWorker(page) + await setConfig({ + page, + config: { + gateways: [process.env.KUBO_GATEWAY as string], + routers: [process.env.KUBO_GATEWAY as string] + } + }) + + await use(page) + } +}) + +/** + * When testing subdomain routing via the service worker, using this fixture will automatically set the config for the subdomain. + * This is useful for testing subdomain routing without having to manually set the config for each subdomain. + * + * @example + * + * ```ts + * import { testSubdomainRouting as test, expect } from './fixtures/config-test-fixtures.js' + * + * test.describe('subdomain-detection', () => { + * test('path requests are redirected to subdomains', async ({ page }) => { + * await page.goto('http://bafkqablimvwgy3y.ipfs.localhost:3333/', { waitUntil: 'networkidle' }) + * const bodyTextLocator = page.locator('body') + * await expect(bodyTextLocator).toContainText('hello') + * }) + * }) + * ``` + */ +export const testSubdomainRouting = base.extend({ + page: async ({ page }, use) => { + await page.goto('http://localhost:3333', { waitUntil: 'networkidle' }) + await waitForServiceWorker(page) + + const oldPageGoto = page.goto.bind(page) + page.goto = async (url: Parameters[0], options: Parameters[1]): ReturnType => { + const response = await oldPageGoto(url, options) + if (['.ipfs.', '.ipns.'].some((part) => url.includes(part))) { + await setSubdomainConfig({ + page, + config: { + autoReload: true, + gateways: [process.env.KUBO_GATEWAY as string], + routers: [process.env.KUBO_GATEWAY as string] + } + }) + } else { + // already set on root. + } + return response + } + + // set config for the initial page + await setConfig({ + page, + config: { + autoReload: true, + gateways: [process.env.KUBO_GATEWAY as string], + routers: [process.env.KUBO_GATEWAY as string] + } + }) + + await use(page) + } +}) + +export { expect } from '@playwright/test' diff --git a/test-e2e/fixtures/create-kubo-node.ts b/test-e2e/fixtures/create-kubo-node.ts index 6cf8590b..bdee1215 100644 --- a/test-e2e/fixtures/create-kubo-node.ts +++ b/test-e2e/fixtures/create-kubo-node.ts @@ -1,6 +1,7 @@ import { createController, type Controller } from 'ipfsd-ctl' import { path as kuboPath } from 'kubo' import * as kuboRpcClient from 'kubo-rpc-client' +import { kuboRepoDir } from './load-kubo-fixtures.js' export async function createKuboNode (): Promise { return createController({ @@ -8,23 +9,7 @@ export async function createKuboNode (): Promise { ipfsBin: kuboPath(), test: true, ipfsOptions: { - config: { - Addresses: { - Swarm: [ - '/ip4/0.0.0.0/tcp/4001', - '/ip4/0.0.0.0/tcp/4002/ws' - ], - Gateway: '/ip4/127.0.0.1/tcp/8180' - }, - Gateway: { - NoFetch: true, - ExposeRoutingAPI: true, - HTTPHeaders: { - 'Access-Control-Allow-Origin': ['*'], - 'Access-Control-Allow-Methods': ['GET', 'POST', 'PUT', 'OPTIONS'] - } - } - } + repo: kuboRepoDir }, args: ['--enable-pubsub-experiment', '--enable-namesys-pubsub'] }) diff --git a/test-e2e/fixtures/do-range-request.ts b/test-e2e/fixtures/do-range-request.ts new file mode 100644 index 00000000..8d11ed3e --- /dev/null +++ b/test-e2e/fixtures/do-range-request.ts @@ -0,0 +1,39 @@ +import type { Page } from '@playwright/test' + +export interface RangeRequestResult { + byteSize: number + /** + * playwright doesn't provide a way to get the raw bytes, so we have to convert the ArrayBuffer to an array of numbers + */ + bytes: number[] + headers: Record + statusCode: number + text: string +} + +/** + * Normally, you could use request.get in playwright to query a server, but this does not go to the service worker + */ +export async function doRangeRequest ({ page, range, path }: { range: string, page: Page, path: string }): Promise { + return page.evaluate(async ({ path, range }) => { + const response = await fetch(path, { headers: { range } }) + const clone = response.clone() + const buffer = await response.arrayBuffer() + const byteSize = buffer.byteLength + const bytes = Array.from(new Uint8Array(buffer)) + const text = await clone.text() + const statusCode = response.status + const headers = {} + response.headers.forEach((value, key) => { + headers[key] = value + }) + + return { + byteSize, + bytes, + headers, + statusCode, + text + } + }, { path, range }) +} diff --git a/test-e2e/fixtures/load-fixture-data.ts b/test-e2e/fixtures/load-fixture-data.ts deleted file mode 100644 index 2fe33309..00000000 --- a/test-e2e/fixtures/load-fixture-data.ts +++ /dev/null @@ -1,9 +0,0 @@ -import loadFixture from 'aegir/fixtures' -import drain from 'it-drain' -import type { Controller } from 'ipfsd-ctl' - -export async function loadFixtureDataCar (controller: Controller, path: string): Promise { - const fixtureData = `test-e2e/fixtures/data/${path}` - const buf = loadFixture(fixtureData) - await drain(controller.api.dag.import([buf])) -} diff --git a/test-e2e/fixtures/load-kubo-fixtures.ts b/test-e2e/fixtures/load-kubo-fixtures.ts new file mode 100644 index 00000000..7354e0b6 --- /dev/null +++ b/test-e2e/fixtures/load-kubo-fixtures.ts @@ -0,0 +1,127 @@ +/** + * This is required to update gateway-conformance fixtures + * + * Can only be ran from node + * + * external command dependencies: + * - `docker` + * - `npx` + */ + +import { readFile } from 'node:fs/promises' +import { dirname, relative, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { logger } from '@libp2p/logger' +import { $ } from 'execa' +import { glob } from 'glob' + +// eslint-disable-next-line @typescript-eslint/naming-convention +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const log = logger('kubo-init') + +// This needs to match the `repo` property provided to `ipfsd-ctl` in `createKuboNode` so our kubo instance in tests use the same repo +export const kuboRepoDir = resolve(__dirname, 'data/test-repo') +export const GWC_FIXTURES_PATH = resolve(__dirname, 'data/gateway-conformance-fixtures') + +export async function loadKuboFixtures (): Promise { + await $`mkdir -p ${kuboRepoDir}` + await $`mkdir -p ${GWC_FIXTURES_PATH}` + + await attemptKuboInit() + + await configureKubo() + + await downloadFixtures() + + await loadFixtures() +} + +function getExecaOptions ({ cwd, ipfsNsMap }: { cwd?: string, ipfsNsMap?: string } = {}): { cwd: string, env: Record } { + return { + cwd: cwd ?? __dirname, + env: { + IPFS_PATH: kuboRepoDir, + IPFS_NS_MAP: ipfsNsMap + } + } +} + +async function attemptKuboInit (): Promise { + const execaOptions = getExecaOptions() + try { + await $(execaOptions)`npx -y kubo init` + log('Kubo initialized at %s', kuboRepoDir) + } catch (e: any) { + if (e.stderr?.includes('ipfs daemon is running') === true) { + log('Kubo is already running') + return + } + if (e.stderr?.includes('already exists!') === true) { + log('Kubo was already initialized at %s', kuboRepoDir) + return + } + + throw e + } +} + +async function configureKubo (): Promise { + const execaOptions = getExecaOptions() + try { + await $(execaOptions)`npx -y kubo config Addresses.Gateway /ip4/127.0.0.1/tcp/8180` + await $(execaOptions)`npx -y kubo config --json Gateway.NoFetch true` + await $(execaOptions)`npx -y kubo config --json Gateway.ExposeRoutingAPI true` + await $(execaOptions)`npx -y kubo config --json Gateway.HTTPHeaders.Access-Control-Allow-Origin '["*"]'` + await $(execaOptions)`npx -y kubo config --json Gateway.HTTPHeaders.Access-Control-Allow-Methods '["GET", "POST", "PUT", "OPTIONS"]'` + log('Kubo configured') + } catch (e) { + log.error('Failed to configure Kubo', e) + } +} + +async function downloadFixtures (force = false): Promise { + if (!force) { + // if the fixtures are already downloaded, we don't need to download them again + const allFixtures = await glob([`${GWC_FIXTURES_PATH}/**/*.car`, `${GWC_FIXTURES_PATH}/**/*.ipns-record`, `${GWC_FIXTURES_PATH}/dnslinks.json`]) + if (allFixtures.length > 0) { + log('Fixtures already downloaded') + return + } + } + + log('Downloading fixtures') + try { + await $`docker run -v ${process.cwd()}:/workspace -w /workspace ghcr.io/ipfs/gateway-conformance:v0.4.2 extract-fixtures --directory ${relative('.', GWC_FIXTURES_PATH)} --merged false` + } catch (e) { + log.error('Error downloading fixtures, assuming current or previous success', e) + } +} + +async function loadFixtures (): Promise { + const execaOptions = getExecaOptions() + + for (const carFile of await glob([`${resolve(__dirname, 'data')}/**/*.car`])) { + log('Loading *.car fixture %s', carFile) + const { stdout } = await $(execaOptions)`npx -y kubo dag import --pin-roots=false --offline ${carFile}` + stdout.split('\n').forEach(log) + } + + // TODO: re-enable this when we resolve CI issue: see https://github.com/ipfs-shipyard/service-worker-gateway/actions/runs/8442583023/job/23124336180?pr=159#step:6:13 + // for (const ipnsRecord of await glob([`${GWC_FIXTURES_PATH}/**/*.ipns-record`])) { + // const key = basename(ipnsRecord, '.ipns-record') + // const relativePath = relative(GWC_FIXTURES_PATH, ipnsRecord) + // log('Loading *.ipns-record fixture %s', relativePath) + // const { stdout } = await $(({ ...execaOptions }))`cd ${GWC_FIXTURES_PATH} && npx -y kubo routing put --allow-offline "/ipns/${key}" "${relativePath}"` + // stdout.split('\n').forEach(log) + // } + + const json = await readFile(`${GWC_FIXTURES_PATH}/dnslinks.json`, 'utf-8') + const { subdomains, domains } = JSON.parse(json) + const subdomainDnsLinks = Object.entries(subdomains).map(([key, value]) => `${key}.example.com:${value}`).join(',') + const domainDnsLinks = Object.entries(domains).map(([key, value]) => `${key}:${value}`).join(',') + const ipfsNsMap = `${domainDnsLinks},${subdomainDnsLinks}` + + // TODO: provide this to kubo instance in tests + return ipfsNsMap +} diff --git a/test-e2e/fixtures/locators.ts b/test-e2e/fixtures/locators.ts index 542cbc7b..199b6025 100644 --- a/test-e2e/fixtures/locators.ts +++ b/test-e2e/fixtures/locators.ts @@ -1,7 +1,10 @@ -import type { Locator, Page } from '@playwright/test' +import type { FrameLocator, Locator, Page } from '@playwright/test' export interface GetLocator { - (page: Page): Locator + (page: Page | FrameLocator): Locator +} +export interface GetFrameLocator { + (page: Page | FrameLocator): FrameLocator } export const getHeader: GetLocator = (page) => page.locator('.e2e-header') @@ -10,4 +13,13 @@ export const getConfigButton: GetLocator = (page) => page.locator('.e2e-header-c export const getConfigPage: GetLocator = (page) => page.locator('.e2e-config-page') export const getConfigPageInput: GetLocator = (page) => page.locator('.e2e-config-page-input') export const getConfigPageButton: GetLocator = (page) => page.locator('.e2e-config-page-button') +export const getIframeLocator: GetFrameLocator = (page) => page.frameLocator('iframe') +export const getConfigGatewaysInput: GetLocator = (page) => page.locator('.e2e-config-page-input-gateways') +export const getConfigRoutersInput: GetLocator = (page) => page.locator('.e2e-config-page-input-routers') export const getConfigAutoReloadInput: GetLocator = (page) => page.locator('.e2e-config-page-input-autoreload') + +export const getConfigButtonIframe: GetLocator = (page) => getIframeLocator(page).locator('.e2e-collapsible-button') +export const getConfigGatewaysInputIframe: GetLocator = (page) => getConfigGatewaysInput(getIframeLocator(page)) +export const getConfigRoutersInputIframe: GetLocator = (page) => getConfigRoutersInput(getIframeLocator(page)) +export const getConfigAutoReloadInputIframe: GetLocator = (page) => getConfigAutoReloadInput(getIframeLocator(page)) +export const getConfigPageButtonIframe: GetLocator = (page) => getConfigPageButton(getIframeLocator(page)) diff --git a/test-e2e/fixtures/set-sw-config.ts b/test-e2e/fixtures/set-sw-config.ts index a3d03a7c..d1a447e1 100644 --- a/test-e2e/fixtures/set-sw-config.ts +++ b/test-e2e/fixtures/set-sw-config.ts @@ -4,13 +4,53 @@ * * Note that this was only tested and confirmed working for subdomain pages. */ +import { getConfigAutoReloadInput, getConfigAutoReloadInputIframe, getConfigButton, getConfigButtonIframe, getConfigGatewaysInput, getConfigGatewaysInputIframe, getConfigPage, getConfigPageButton, getConfigPageButtonIframe, getConfigRoutersInput, getConfigRoutersInputIframe } from './locators.js' import { waitForServiceWorker } from './wait-for-service-worker.js' import type { ConfigDb } from '../../src/lib/config-db.js' import type { Page } from '@playwright/test' +export async function setConfigViaUiSubdomain ({ page, config }: { page: Page, config: Partial }): Promise { + await waitForServiceWorker(page) + + await getConfigButtonIframe(page).isVisible() + await getConfigButtonIframe(page).click() + + for (const [key] of Object.entries(config)) { + if (key === 'autoReload') { + await getConfigAutoReloadInputIframe(page).click() + } + } + await getConfigGatewaysInputIframe(page).locator('input').fill(JSON.stringify([process.env.KUBO_GATEWAY])) + await getConfigRoutersInputIframe(page).locator('input').fill(JSON.stringify([process.env.KUBO_GATEWAY])) + + await getConfigPageButtonIframe(page).click() + + await getConfigPage(page).isHidden() +} + +export async function setConfigViaUi ({ page, config }: { page: Page, config: Partial }): Promise { + await waitForServiceWorker(page) + + await getConfigButton(page).isVisible() + await getConfigButton(page).click() + await getConfigPage(page).isVisible() + + for (const [key] of Object.entries(config)) { + if (key === 'autoReload') { + await getConfigAutoReloadInput(page).click() + } + } + + await getConfigGatewaysInput(page).locator('input').fill(JSON.stringify([process.env.KUBO_GATEWAY])) + await getConfigRoutersInput(page).locator('input').fill(JSON.stringify([process.env.KUBO_GATEWAY])) + + await getConfigPageButton(page).click() + + await getConfigPage(page).isHidden() +} + // TODO: ensure that the config can be set on root and loaded properly by subdomains with playwright export async function setConfig ({ page, config }: { page: Page, config: Partial }): Promise { - await waitForServiceWorker(page) // we can't pass through functions we already have defined, so many of these things are copied over from /src/lib/generic-db.ts await page.evaluate(async (configInPage) => { const dbName = 'helia-sw' @@ -58,7 +98,22 @@ export async function setConfig ({ page, config }: { page: Page, config: Partial }) channel.postMessage({ target: 'SW', action: 'RELOAD_CONFIG', source: 'WINDOW' }) await swResponsePromise + }, { + gateways: [process.env.KUBO_GATEWAY], + routers: [process.env.KUBO_GATEWAY], + ...config + }) +} + +export async function setSubdomainConfig ({ page, config }: { page: Page, config: Partial }): Promise { + await waitForServiceWorker(page) + + await page.evaluate(async (configInPage) => { // TODO: we shouldn't need this. We should be able to just post a message to the service worker to reload it's config. window.postMessage({ source: 'helia-sw-config-iframe', target: 'PARENT', action: 'RELOAD_CONFIG', config: configInPage }, { targetOrigin: window.location.origin }) - }, config) + }, { + gateways: [process.env.KUBO_GATEWAY], + routers: [process.env.KUBO_GATEWAY], + ...config + }) } diff --git a/test-e2e/global-setup.js b/test-e2e/global-setup.js deleted file mode 100644 index e8ec84b1..00000000 --- a/test-e2e/global-setup.js +++ /dev/null @@ -1,17 +0,0 @@ -import { chromium } from '@playwright/test' - -/** - * TODO: we may want to set up a kubo daemon for testing fetching of content - * see https://github.com/ipfs/ipfs-webui/blob/main/test/e2e/setup/global-setup.js for inspiration - * - * @param {import('@playwright/test').Config} config - */ -const globalSetup = async (config) => { - const baseURL = 'http://localhost:3000' - const browser = await chromium.launch() - const page = await browser.newPage() - await page.goto(baseURL) - await browser.close() -} - -export default globalSetup diff --git a/test-e2e/global-setup.ts b/test-e2e/global-setup.ts new file mode 100644 index 00000000..774f34f1 --- /dev/null +++ b/test-e2e/global-setup.ts @@ -0,0 +1,13 @@ +import { type Config } from '@playwright/test' +import { createKuboNode } from './fixtures/create-kubo-node.js' +import { loadKuboFixtures } from './fixtures/load-kubo-fixtures.js' + +export default async function globalSetup (config: Config): Promise { + await loadKuboFixtures() + const controller = await createKuboNode() + await controller.start() + + process.env.KUBO_PID = `${await controller.pid()}` + const gateway = `http://${controller.api.gatewayHost}:${controller.api.gatewayPort}` + process.env.KUBO_GATEWAY = gateway +} diff --git a/test-e2e/global-teardown.ts b/test-e2e/global-teardown.ts new file mode 100644 index 00000000..2dc5261e --- /dev/null +++ b/test-e2e/global-teardown.ts @@ -0,0 +1,7 @@ +import { type Config } from '@playwright/test' +import { $ } from 'execa' +import { kuboRepoDir } from './fixtures/load-kubo-fixtures.js' + +export default async function globalTeardown (config: Config): Promise { + await $`rm -rf ${kuboRepoDir}` +} diff --git a/test-e2e/path-routing.test.ts b/test-e2e/path-routing.test.ts index 1f97e6b5..096ba9ee 100644 --- a/test-e2e/path-routing.test.ts +++ b/test-e2e/path-routing.test.ts @@ -1,12 +1,7 @@ -import { test, expect } from '@playwright/test' -import { waitForServiceWorker } from './fixtures/wait-for-service-worker.js' +import { testPathRouting as test, expect } from './fixtures/config-test-fixtures.js' test.describe('path-routing', () => { test('can load identity CID via path', async ({ page }) => { - // explicitly loading at 127.0.0.1 so subdomain redirection is not triggered - await page.goto('http://127.0.0.1:3333', { waitUntil: 'networkidle' }) - // wait for service worker to load - await waitForServiceWorker(page) const response = await page.goto('http://127.0.0.1:3333/ipfs/bafkqablimvwgy3y', { waitUntil: 'networkidle' }) expect(response?.status()).toBe(200) diff --git a/test-e2e/subdomain-detection.test.ts b/test-e2e/subdomain-detection.test.ts index 6675e5a9..7badf0f3 100644 --- a/test-e2e/subdomain-detection.test.ts +++ b/test-e2e/subdomain-detection.test.ts @@ -1,13 +1,13 @@ -import { test, expect } from '@playwright/test' -import { setConfig } from './fixtures/set-sw-config.js' +import { test } from '@playwright/test' +import { testSubdomainRouting, expect } from './fixtures/config-test-fixtures.js' +import { setConfig, setSubdomainConfig } from './fixtures/set-sw-config.js' import { waitForServiceWorker } from './fixtures/wait-for-service-worker.js' test.describe('subdomain-detection', () => { - test('path requests are redirected to subdomains', async ({ page, context }) => { - await page.goto('/', { waitUntil: 'networkidle' }) - // wait for service worker to load on main url + test('path requests are redirected to subdomains', async ({ page }) => { + await page.goto('http://localhost:3333', { waitUntil: 'networkidle' }) await waitForServiceWorker(page) - + await setConfig({ page, config: { autoReload: false, gateways: [process.env.KUBO_GATEWAY as string], routers: [process.env.KUBO_GATEWAY as string] } }) const initialResponse = await page.goto('/ipfs/bafkqablimvwgy3y', { waitUntil: 'commit' }) expect(initialResponse?.url()).toBe('http://bafkqablimvwgy3y.ipfs.localhost:3333/') @@ -25,17 +25,19 @@ test.describe('subdomain-detection', () => { await expect(bodyTextLocator).toContainText('hello') }) - test('path requests are redirected to subdomains automatically with autoreload enabled', async ({ page, context }) => { - await page.goto('/', { waitUntil: 'networkidle' }) - await waitForServiceWorker(page) + test('enabling autoreload automatically loads the subdomain', async ({ page }) => { + await page.goto('http://bafkqablimvwgy3y.ipfs.localhost:3333/', { waitUntil: 'networkidle' }) + await setSubdomainConfig({ page, config: { autoReload: true, gateways: [process.env.KUBO_GATEWAY as string], routers: [process.env.KUBO_GATEWAY as string] } }) - const initialResponse = await page.goto('/ipfs/bafkqablimvwgy3y', { waitUntil: 'commit' }) + const bodyTextLocator = page.locator('body') - expect(initialResponse?.url()).toBe('http://bafkqablimvwgy3y.ipfs.localhost:3333/') - expect(initialResponse?.request()?.redirectedFrom()?.url()).toBe('http://localhost:3333/ipfs/bafkqablimvwgy3y') + await expect(bodyTextLocator).toContainText('hello') + }) +}) - await page.waitForURL('http://bafkqablimvwgy3y.ipfs.localhost:3333') - await setConfig({ page, config: { autoReload: true } }) +testSubdomainRouting.describe('subdomain-detection auto fixture', () => { + testSubdomainRouting('loads subdomains easily', async ({ page }) => { + await page.goto('http://bafkqablimvwgy3y.ipfs.localhost:3333/', { waitUntil: 'networkidle' }) const bodyTextLocator = page.locator('body') diff --git a/test-e2e/website-loading.test.ts b/test-e2e/website-loading.test.ts index fd8002d6..e27ac23e 100644 --- a/test-e2e/website-loading.test.ts +++ b/test-e2e/website-loading.test.ts @@ -1,42 +1,16 @@ -import { test, expect } from '@playwright/test' -import { createKuboNode } from './fixtures/create-kubo-node.js' -import { loadFixtureDataCar } from './fixtures/load-fixture-data.js' -import { setConfig } from './fixtures/set-sw-config.js' -import { waitForServiceWorker } from './fixtures/wait-for-service-worker.js' -import type { ConfigDb } from '../src/lib/config-db.js' -import type { Controller } from 'ipfsd-ctl' +import { testPathRouting as test, expect } from './fixtures/config-test-fixtures.js' test.describe('website-loading', () => { - let controller: Controller<'go'> - let config: Partial - - test.beforeAll(async () => { - controller = await createKuboNode() - await controller.start() - await loadFixtureDataCar(controller, 'gateway-conformance-fixtures.car') - config = { - gateways: [`http://${controller.api.gatewayHost}:${controller.api.gatewayPort}`], - routers: [`http://${controller.api.gatewayHost}:${controller.api.gatewayPort}`] - } - }) - - test.beforeEach(async ({ page }) => { - // not testing subdomain redirection here - await page.goto('http://127.0.0.1:3000', { waitUntil: 'networkidle' }) - await setConfig({ page, config }) - await waitForServiceWorker(page) - }) - test('ensure unixfs directory trailing slash is added', async ({ page }) => { - const response = await page.goto('http://127.0.0.1:3000/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q') + const response = await page.goto('http://127.0.0.1:3333/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q') // playwright follows redirects so we won't see the 301 expect(response?.status()).toBe(200) - expect(response?.url()).toBe('http://127.0.0.1:3000/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q/') + expect(response?.url()).toBe('http://127.0.0.1:3333/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q/') }) test('ensure that index.html is returned for the root path', async ({ page }) => { - const response = await page.goto('http://127.0.0.1:3000/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q/') + const response = await page.goto('http://127.0.0.1:3333/ipfs/bafybeifq2rzpqnqrsdupncmkmhs3ckxxjhuvdcbvydkgvch3ms24k5lo7q/') expect(response?.status()).toBe(200) const headers = await response?.allHeaders()