From 02c060059b1022e25a62af2f49fc7d4944a76320 Mon Sep 17 00:00:00 2001 From: isaackps Date: Wed, 24 Sep 2025 12:51:53 +0800 Subject: [PATCH 01/12] fix: add GitHub Actions workflow for automated S3 deployment --- .github/workflows/deploy-s3.yml | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/deploy-s3.yml diff --git a/.github/workflows/deploy-s3.yml b/.github/workflows/deploy-s3.yml new file mode 100644 index 0000000..be5dfee --- /dev/null +++ b/.github/workflows/deploy-s3.yml @@ -0,0 +1,66 @@ +name: Deploy to S3 + +on: + push: + branches: [ master ] + workflow_dispatch: + +concurrency: + group: deploy-s3-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + name: Build and Deploy to S3 + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Cache npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }} + ${{ runner.os }}-build + ${{ runner.os }} + + - name: Install Dependencies + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' + run: npm i + + - name: Build + run: npm run build + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Sync assets to S3 + run: | + aws s3 sync ./dist "s3://${{ secrets.S3_BUCKET }}/" --delete + + - name: Invalidate CloudFront (optional) + env: + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + if: ${{ env.CLOUDFRONT_DISTRIBUTION_ID != '' }} + run: | + aws cloudfront create-invalidation \ + --distribution-id "${{ env.CLOUDFRONT_DISTRIBUTION_ID }}" \ + --paths "/*" + From 12a5579aa2d710fad217e2c9b8d1c649487c7824 Mon Sep 17 00:00:00 2001 From: isaackps Date: Wed, 24 Sep 2025 12:59:24 +0800 Subject: [PATCH 02/12] fix: update yml --- .github/workflows/deploy-s3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-s3.yml b/.github/workflows/deploy-s3.yml index be5dfee..bedd1c9 100644 --- a/.github/workflows/deploy-s3.yml +++ b/.github/workflows/deploy-s3.yml @@ -2,7 +2,7 @@ name: Deploy to S3 on: push: - branches: [ master ] + branches: [ imda ] workflow_dispatch: concurrency: From 6c495d903716edf218ca341f9623033a12b7b477 Mon Sep 17 00:00:00 2001 From: isaackps Date: Tue, 4 Nov 2025 16:03:00 +0800 Subject: [PATCH 03/12] ci: add dev deployment workflow and rename prod workflow for S3/CloudFront deployments --- .github/workflows/deploy-s3-dev.yml | 64 +++++++++++++++++++ .../{deploy-s3.yml => deploy-s3-prod.yml} | 15 ++--- 2 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/deploy-s3-dev.yml rename .github/workflows/{deploy-s3.yml => deploy-s3-prod.yml} (87%) diff --git a/.github/workflows/deploy-s3-dev.yml b/.github/workflows/deploy-s3-dev.yml new file mode 100644 index 0000000..030f67f --- /dev/null +++ b/.github/workflows/deploy-s3-dev.yml @@ -0,0 +1,64 @@ +name: Deploy to S3 (Dev) + +on: + push: + branches: [develop] + +concurrency: + group: deploy-s3-dev-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + name: Build and Deploy to S3 (Dev) + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "18" + + - name: Cache npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }} + ${{ runner.os }}-build + ${{ runner.os }} + + - name: Install Dependencies + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true" + run: npm i + + - name: Build + run: npm run build + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Sync assets to S3 + run: | + aws s3 sync ./dist "s3://${{ secrets.S3_BUCKET_DEV }}/" --delete + + - name: Invalidate CloudFront (optional) + env: + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_DEV }} + if: ${{ env.CLOUDFRONT_DISTRIBUTION_ID != '' }} + run: | + aws cloudfront create-invalidation \ + --distribution-id "${{ env.CLOUDFRONT_DISTRIBUTION_ID }}" \ + --paths "/*" diff --git a/.github/workflows/deploy-s3.yml b/.github/workflows/deploy-s3-prod.yml similarity index 87% rename from .github/workflows/deploy-s3.yml rename to .github/workflows/deploy-s3-prod.yml index bedd1c9..1a07265 100644 --- a/.github/workflows/deploy-s3.yml +++ b/.github/workflows/deploy-s3-prod.yml @@ -1,17 +1,15 @@ -name: Deploy to S3 +name: Deploy to S3 (Prod) on: - push: - branches: [ imda ] workflow_dispatch: concurrency: - group: deploy-s3-${{ github.ref }} + group: deploy-s3-prod-${{ github.ref }} cancel-in-progress: true jobs: deploy: - name: Build and Deploy to S3 + name: Build and Deploy to S3 (Prod) runs-on: ubuntu-latest timeout-minutes: 15 @@ -22,7 +20,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v3 with: - node-version: '18' + node-version: "18" - name: Cache npm uses: actions/cache@v3 @@ -38,7 +36,7 @@ jobs: - name: Install Dependencies env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true" run: npm i - name: Build @@ -57,10 +55,9 @@ jobs: - name: Invalidate CloudFront (optional) env: - CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID_PROD }} if: ${{ env.CLOUDFRONT_DISTRIBUTION_ID != '' }} run: | aws cloudfront create-invalidation \ --distribution-id "${{ env.CLOUDFRONT_DISTRIBUTION_ID }}" \ --paths "/*" - From 5bdf757817c96d3e127e995049f4c66b40ee4353 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Mon, 6 Jul 2026 15:46:20 +0530 Subject: [PATCH 04/12] fix add migrate to trustvc banner --- src/app.tsx | 15 +++++++++-- src/components/common/migration-banner.tsx | 29 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/components/common/migration-banner.tsx diff --git a/src/app.tsx b/src/app.tsx index fe6b7b7..d5a191f 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -4,6 +4,7 @@ import { MainPage } from "./page/main-page"; import { MetaMaskError } from "./page/metamask-error"; import { MetaMaskPending } from "./page/metamask-pending"; import { EthereumProviderError } from "eth-rpc-errors"; +import { MigrationBanner } from "./components/common/migration-banner"; const App: FunctionComponent = () => { const [metamaskConnected, setMetamaskConnected] = useState(false); @@ -30,10 +31,20 @@ const App: FunctionComponent = () => { }; if (!window.ethereum) { - return ; + return ( + <> + + + + ); } - return metamaskConnected ? : ; + return ( + <> + + {metamaskConnected ? : } + + ); }; export default App; diff --git a/src/components/common/migration-banner.tsx b/src/components/common/migration-banner.tsx new file mode 100644 index 0000000..4b08039 --- /dev/null +++ b/src/components/common/migration-banner.tsx @@ -0,0 +1,29 @@ +import React, { FunctionComponent } from "react"; + +// TODO: replace with the real migration guide URL +const MIGRATION_GUIDE_URL = "https://docs.tradetrust.io/docs/migration-guide/trustvc"; + +export const MigrationBanner: FunctionComponent = () => ( +
+ Please note that as of 1 October 2025, OpenAttestation (OA) has been deprecated. OpenCerts has since migrated to + TrustVC, which uses the W3C Verifiable Credentials (VC) format. +
+ Documents previously issued in OA format remain verifiable — no action is required for existing certificates. + However, we recommend upgrading to the W3C VC format for all new issuances. +
+ To migrate, please refer to our{" "} + + migration guide + + . +
+); From e64c74fffe3dbe302a826384e43c0172248b533d Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Tue, 7 Jul 2026 09:08:47 +0530 Subject: [PATCH 05/12] fix: update baner link --- src/components/common/migration-banner.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/common/migration-banner.tsx b/src/components/common/migration-banner.tsx index 4b08039..cc61e56 100644 --- a/src/components/common/migration-banner.tsx +++ b/src/components/common/migration-banner.tsx @@ -1,7 +1,6 @@ import React, { FunctionComponent } from "react"; -// TODO: replace with the real migration guide URL -const MIGRATION_GUIDE_URL = "https://docs.tradetrust.io/docs/migration-guide/trustvc"; +const MIGRATION_GUIDE_URL = "https://docs.opencerts.io/docs/migrations/oa_to_trustvc"; export const MigrationBanner: FunctionComponent = () => (
Date: Tue, 7 Jul 2026 16:16:24 +0530 Subject: [PATCH 06/12] fix: update tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc3e733..f003f4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: test: name: Lint & Test runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 25 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 From 8efd67d806ed2395059bdc08c411945263f5a057 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 11:01:10 +0530 Subject: [PATCH 07/12] fix: update tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f003f4c..98fd8a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Install Dependencies env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' + PUPPETEER_SKIP_DOWNLOAD: 'true' run: npm i - name: Check Lint From 221c951371d59013c67670fe2e5d79a448bc5eb7 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 13:19:56 +0530 Subject: [PATCH 08/12] fix: update tests --- integration/issue.mjs | 2 +- integration/metamask-init.mjs | 2 +- package.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/issue.mjs b/integration/issue.mjs index 29b9308..ce6dd0c 100644 --- a/integration/issue.mjs +++ b/integration/issue.mjs @@ -4,7 +4,7 @@ import { sleep, waitForAndClickElement, waitForElement } from "./utils.mjs"; export const issue = async (metamask, browser) => { try { const page = await browser.newPage(); - await page.goto("http://localhost:4173/", { waitUntil: "networkidle" }); + await page.goto("http://localhost:4173/", { waitUntil: "networkidle0" }); // START - approve application once, subsequent tests no longer need await metamask.approve(); diff --git a/integration/metamask-init.mjs b/integration/metamask-init.mjs index 54256c7..1489cf1 100644 --- a/integration/metamask-init.mjs +++ b/integration/metamask-init.mjs @@ -33,7 +33,7 @@ export const metamaskInit = async () => { // Add network // https://github.com/ChainSafe/dappeteer/blob/b79ab4c74fab87747933d8f428624dcbffc3dd19/test/basic.spec.ts#L117-L119 const dappPage = await browser.newPage(); - await dappPage.goto("https://github.com", { waitUntil: "networkidle" }); + await dappPage.goto("https://github.com", { waitUntil: "networkidle0" }); dappPage.evaluate(addNetwork); try { diff --git a/package.json b/package.json index 20c415a..d68c22b 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,13 @@ "start": "npm-run-all -p start:*", "start:css": "postcss src/tailwind.css -o src/index.css --watch", "start:dev": "sleep 3 && vite", - "serve": "npm run build && vite preview", + "serve": "npm run build && vite preview --host 127.0.0.1 --port 4173", "test": "jest", "test:watch": "jest --watch", "integration:blockchain": "ganache --mnemonic \"indicate swing place chair flight used hammer soon photo region volume shuffle\"", "integration:local": "npm run serve", "integration:metamask": "npm-run-all --serial integration:wait \"integration:transfers {1} \" --", - "integration:wait": "wait-on -l -i 1000 tcp:8545 http-get://localhost:4173", + "integration:wait": "wait-on -l -i 1000 -t 60000 tcp:8545 http-get://127.0.0.1:4173", "integration:transfers": "node --experimental-modules ./integration/index.mjs", "integration:dappeteer": "npm-run-all --parallel integration:blockchain integration:local \"integration:metamask {1}\" --race --", "lint": "eslint ./src ./integration --ext .js,.ts,.tsx,.mjs --max-warnings 0", From 6fbd8612a08a89254acfd5360b32351caa2033af Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 14:29:03 +0530 Subject: [PATCH 09/12] fix: update tests --- integration/prefetch-metamask.mjs | 81 +++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 integration/prefetch-metamask.mjs diff --git a/integration/prefetch-metamask.mjs b/integration/prefetch-metamask.mjs new file mode 100644 index 0000000..37938b2 --- /dev/null +++ b/integration/prefetch-metamask.mjs @@ -0,0 +1,81 @@ +import https from "https"; +import fs from "fs"; +import path from "path"; +import { execSync } from "child_process"; + +// dappeteer's own downloader only reads page 1 (30 releases) of the GitHub API with no +// pagination, so an older pinned version falls off the list as MetaMask keeps shipping +// releases. Pre-populating dappeteer's cache directory here makes it skip that search +// entirely (it only searches when the extraction directory is missing). +const METAMASK_VERSION = "v10.31.0"; +const CACHE_DIR = path.resolve("node_modules", ".cache", ".metamask"); +const EXTRACT_DIR = path.resolve(CACHE_DIR, METAMASK_VERSION.replace(/\./g, "_")); +const DOWNLOAD_DIR = path.resolve(CACHE_DIR, "download"); +const RELEASES_URL = "https://api.github.com/repos/metamask/metamask-extension/releases"; +const MAX_PAGES = 10; + +const getJson = (url) => + new Promise((resolve, reject) => { + https + .get(url, { headers: { "User-Agent": "Mozilla/5.0" } }, (res) => { + let body = ""; + res.on("data", (chunk) => (body += chunk)); + res.on("end", () => resolve(JSON.parse(body))); + }) + .on("error", reject); + }); + +const findChromeAssetUrl = async () => { + for (let page = 1; page <= MAX_PAGES; page++) { + const releases = await getJson(`${RELEASES_URL}?per_page=100&page=${page}`); + if (!Array.isArray(releases) || releases.length === 0) break; + + const release = releases.find((r) => r.tag_name === METAMASK_VERSION); + if (release) { + const asset = release.assets.find((a) => a.name.startsWith("metamask-chrome-")); + if (asset) return asset.browser_download_url; + } + } + throw new Error(`Could not find MetaMask ${METAMASK_VERSION} chrome release asset`); +}; + +const download = (url, dest) => + new Promise((resolve, reject) => { + const request = (requestUrl) => + https + .get(requestUrl, (res) => { + if (res.statusCode === 301 || res.statusCode === 302) { + request(res.headers.location); + return; + } + const file = fs.createWriteStream(dest); + res.pipe(file); + file.on("finish", () => file.close(resolve)); + }) + .on("error", reject); + request(url); + }); + +const main = async () => { + if (fs.existsSync(EXTRACT_DIR)) { + console.info(`MetaMask ${METAMASK_VERSION} already cached at ${EXTRACT_DIR}`); + return; + } + + console.info(`Searching MetaMask releases for ${METAMASK_VERSION}...`); + const downloadUrl = await findChromeAssetUrl(); + + fs.mkdirSync(DOWNLOAD_DIR, { recursive: true }); + const zipPath = path.resolve(DOWNLOAD_DIR, `${METAMASK_VERSION}.zip`); + console.info(`Downloading ${downloadUrl}`); + await download(downloadUrl, zipPath); + + fs.mkdirSync(EXTRACT_DIR, { recursive: true }); + execSync(`unzip -q -o "${zipPath}" -d "${EXTRACT_DIR}"`); + console.info(`Extracted MetaMask ${METAMASK_VERSION} to ${EXTRACT_DIR}`); +}; + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package.json b/package.json index d68c22b..6e18d5c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "test:watch": "jest --watch", "integration:blockchain": "ganache --mnemonic \"indicate swing place chair flight used hammer soon photo region volume shuffle\"", "integration:local": "npm run serve", - "integration:metamask": "npm-run-all --serial integration:wait \"integration:transfers {1} \" --", + "integration:metamask": "npm-run-all --serial integration:prefetch-metamask integration:wait \"integration:transfers {1} \" --", + "integration:prefetch-metamask": "node ./integration/prefetch-metamask.mjs", "integration:wait": "wait-on -l -i 1000 -t 60000 tcp:8545 http-get://127.0.0.1:4173", "integration:transfers": "node --experimental-modules ./integration/index.mjs", "integration:dappeteer": "npm-run-all --parallel integration:blockchain integration:local \"integration:metamask {1}\" --race --", From d9a335662b854959147a2f4f1d029bf06e78e960 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 15:15:59 +0530 Subject: [PATCH 10/12] fix: update tests --- .github/workflows/ci.yml | 8 ++++++ integration/metamask-init.mjs | 51 ++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98fd8a2..b031230 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,3 +47,11 @@ jobs: CI: "true" with: args: npm run integration:dappeteer + + - name: Upload integration debug screenshot + if: always() + uses: actions/upload-artifact@v4 + with: + name: integration-debug + path: integration/debug-metamask-timeout.png + if-no-files-found: ignore diff --git a/integration/metamask-init.mjs b/integration/metamask-init.mjs index 1489cf1..08cfe7c 100644 --- a/integration/metamask-init.mjs +++ b/integration/metamask-init.mjs @@ -2,6 +2,45 @@ import dappeteer from "@chainsafe/dappeteer"; import { addNetwork } from "./addNetwork.mjs"; import { sleep } from "./utils.mjs"; +// dappeteer's setupMetaMask() waits on a browser "targetcreated" event with no timeout +// and no fallback if the extension never opens its home.html tab. Wrap it so a stuck +// extension fails fast with diagnostics instead of hanging until the CI job is killed. +const withDiagnosticTimeout = (promise, browser, ms) => + new Promise((resolve, reject) => { + const timer = setTimeout(() => { + dumpDiagnostics(browser).finally(() => { + reject(new Error(`Timed out after ${ms}ms waiting for MetaMask setup`)); + }); + }, ms); + promise.then( + (value) => { + clearTimeout(timer); + resolve(value); + }, + (error) => { + clearTimeout(timer); + reject(error); + } + ); + }); + +const dumpDiagnostics = async (browser) => { + try { + const pages = await browser.pages(); + console.info(`Open pages at timeout (${pages.length}):`); + for (const [i, page] of pages.entries()) { + console.info(` [${i}] ${page.url()}`); + } + const lastPage = pages[pages.length - 1]; + if (lastPage) { + await lastPage.screenshot({ path: "integration/debug-metamask-timeout.png" }); + console.info("Saved screenshot to integration/debug-metamask-timeout.png"); + } + } catch (diagError) { + console.error("Failed to capture MetaMask timeout diagnostics:", diagError); + } +}; + export const metamaskInit = async () => { try { const browser = await dappeteer.launch({ @@ -25,10 +64,14 @@ export const metamaskInit = async () => { } // Get metamask - const metamask = await dappeteer.setupMetaMask(browser, { - seed: "indicate swing place chair flight used hammer soon photo region volume shuffle", - showTestNets: true, - }); + const metamask = await withDiagnosticTimeout( + dappeteer.setupMetaMask(browser, { + seed: "indicate swing place chair flight used hammer soon photo region volume shuffle", + showTestNets: true, + }), + browser, + 45000 + ); // Add network // https://github.com/ChainSafe/dappeteer/blob/b79ab4c74fab87747933d8f428624dcbffc3dd19/test/basic.spec.ts#L117-L119 From 09cda0777574fd7fab849e2de98cfa514fd1e36d Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 15:28:05 +0530 Subject: [PATCH 11/12] fix: update tests --- integration/metamask-init.mjs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/integration/metamask-init.mjs b/integration/metamask-init.mjs index 08cfe7c..997a395 100644 --- a/integration/metamask-init.mjs +++ b/integration/metamask-init.mjs @@ -5,10 +5,10 @@ import { sleep } from "./utils.mjs"; // dappeteer's setupMetaMask() waits on a browser "targetcreated" event with no timeout // and no fallback if the extension never opens its home.html tab. Wrap it so a stuck // extension fails fast with diagnostics instead of hanging until the CI job is killed. -const withDiagnosticTimeout = (promise, browser, ms) => +const withDiagnosticTimeout = (promise, rawBrowser, ms) => new Promise((resolve, reject) => { const timer = setTimeout(() => { - dumpDiagnostics(browser).finally(() => { + dumpDiagnostics(rawBrowser).finally(() => { reject(new Error(`Timed out after ${ms}ms waiting for MetaMask setup`)); }); }, ms); @@ -24,16 +24,16 @@ const withDiagnosticTimeout = (promise, browser, ms) => ); }); -const dumpDiagnostics = async (browser) => { +const dumpDiagnostics = async (rawBrowser) => { try { - const pages = await browser.pages(); + const pages = await rawBrowser.pages(); console.info(`Open pages at timeout (${pages.length}):`); for (const [i, page] of pages.entries()) { console.info(` [${i}] ${page.url()}`); } const lastPage = pages[pages.length - 1]; if (lastPage) { - await lastPage.screenshot({ path: "integration/debug-metamask-timeout.png" }); + await lastPage.screenshot({ path: "integration/debug-metamask-timeout.png", fullPage: true }); console.info("Saved screenshot to integration/debug-metamask-timeout.png"); } } catch (diagError) { @@ -41,6 +41,14 @@ const dumpDiagnostics = async (browser) => { } }; +const wireConsoleLogging = (page, label) => { + page.on("console", (msg) => console.info(`[console ${label}] ${msg.text()}`)); + page.on("pageerror", (err) => console.info(`[pageerror ${label}] ${err}`)); + page.on("requestfailed", (req) => { + console.info(`[requestfailed ${label}] ${req.url()} ${req.failure()?.errorText ?? ""}`); + }); +}; + export const metamaskInit = async () => { try { const browser = await dappeteer.launch({ @@ -54,6 +62,21 @@ export const metamaskInit = async () => { slowMo: process.argv[2] || 0, }, }); + const rawBrowser = browser.getSource(); + + // Surface every target and its console/page errors as soon as they happen, so a + // failed extension load is visible instead of a silent hang. + rawBrowser.on("targetcreated", async (target) => { + console.info(`[target created] type=${target.type()} url=${target.url()}`); + const page = await target.page().catch(() => null); + if (page) { + wireConsoleLogging(page, target.url()); + } + }); + for (const page of await rawBrowser.pages()) { + console.info(`[existing target] url=${page.url()}`); + wireConsoleLogging(page, page.url()); + } await sleep(2000); const pages = await browser.pages(); @@ -69,7 +92,7 @@ export const metamaskInit = async () => { seed: "indicate swing place chair flight used hammer soon photo region volume shuffle", showTestNets: true, }), - browser, + rawBrowser, 45000 ); From eb9d366fe392a2b0e1e7818115701618478b6e18 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 8 Jul 2026 15:39:12 +0530 Subject: [PATCH 12/12] fix: update tests --- integration/metamask-init.mjs | 7 ++++++- package.json | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/integration/metamask-init.mjs b/integration/metamask-init.mjs index 997a395..cabd2d8 100644 --- a/integration/metamask-init.mjs +++ b/integration/metamask-init.mjs @@ -57,7 +57,12 @@ export const metamaskInit = async () => { headless: false, puppeteerOptions: { args: ["--no-sandbox"], - executablePath: process.env.PUPPETEER_EXEC_PATH, + // Deliberately NOT using process.env.PUPPETEER_EXEC_PATH here: the + // puppeteer-headful Docker image points it at the system's current + // google-chrome-stable, which no longer supports the Manifest V2 + // extension format this pinned MetaMask build uses. Puppeteer's own + // bundled Chrome (pinned to a version from when MV2 still worked) + // is fetched by the integration:prefetch-chromium script instead. defaultViewport: null, slowMo: process.argv[2] || 0, }, diff --git a/package.json b/package.json index 6e18d5c..9ffd6cd 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,9 @@ "test:watch": "jest --watch", "integration:blockchain": "ganache --mnemonic \"indicate swing place chair flight used hammer soon photo region volume shuffle\"", "integration:local": "npm run serve", - "integration:metamask": "npm-run-all --serial integration:prefetch-metamask integration:wait \"integration:transfers {1} \" --", + "integration:metamask": "npm-run-all --serial integration:prefetch-metamask integration:prefetch-chromium integration:wait \"integration:transfers {1} \" --", "integration:prefetch-metamask": "node ./integration/prefetch-metamask.mjs", + "integration:prefetch-chromium": "node node_modules/puppeteer/install.mjs", "integration:wait": "wait-on -l -i 1000 -t 60000 tcp:8545 http-get://127.0.0.1:4173", "integration:transfers": "node --experimental-modules ./integration/index.mjs", "integration:dappeteer": "npm-run-all --parallel integration:blockchain integration:local \"integration:metamask {1}\" --race --",