Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b29c3a6
[Core] - Update Keplr wallet version
amaify Jun 23, 2026
96edc41
Merge pull request #46 from amaify/tobe/no-ref/update-keplr-wallet-ve…
Jun 23, 2026
2d326c1
[Core] - Pin Playwright version to v1.61.0
amaify Jun 23, 2026
811a8ea
Merge pull request #47 from amaify/tobe/no-ref/update-keplr-wallet-ve…
amaify Jun 23, 2026
b3dab22
[All] - Fix failing tests, update actions and downgrade Playwright
amaify Jun 24, 2026
a4017c9
[CI] - Fix failing CI
amaify Jun 24, 2026
6154388
[CI] - fix Playwright browser installation command
amaify Jun 24, 2026
d5fe79d
Merge pull request #48 from amaify/tobe/no-ref/fix-failing-tests
Jun 24, 2026
5cf5369
[MetaMask] - Fix issues with MetaMask
amaify Jun 24, 2026
ad6fc4a
[Wallets] - Code improvement and update playwright config for CI tests.
amaify Jun 24, 2026
324e8aa
[CI] - Upload the artifacts.
amaify Jun 24, 2026
aa65408
[CI] - Upload Playwright artifacts.
amaify Jun 24, 2026
491f504
chore: fix Metamask's onboarding flow.
amaify Jun 24, 2026
b2fd23e
Merge pull request #49 from amaify/tobe/no-ref/improve-metamask
amaify Jun 24, 2026
9393d35
[README] - Update the README.md
amaify Jun 24, 2026
86c77fe
[Core] - Verify file integrity for local files
amaify Jun 24, 2026
c9c90d8
Merge pull request #50 from amaify/tobe/no-ref/update-readme
amaify Jun 24, 2026
ae8f1e0
[CI] - Use the latest "actions/upload-artifact" version.
amaify Jun 24, 2026
bcf59de
Merge pull request #51 from amaify/tobe/no-ref/use-upload-artifact-v7
amaify Jun 24, 2026
15b57c6
[Release] - Release v0.10.1
amaify Jun 24, 2026
81e8923
Merge pull request #52 from amaify/tobe/no-ref/release
amaify Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/chainwright.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
run: sudo apt-get install -y xvfb

- name: Install Playwright browsers
run: pnpx playwright install chromium
run: pnpm exec playwright install chromium

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
Expand All @@ -97,3 +97,14 @@ jobs:
- name: Skip e2e tests (no relevant file changes)
if: github.event_name != 'workflow_dispatch' && steps.changed-tests.outputs.test_files == ''
run: echo "No relevant test files changed - skipping tests"

- name: Upload Playwright artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: playwright-test-results
path: |
test-results/
tests/test-results/
retention-days: 4
if-no-files-found: ignore
32 changes: 32 additions & 0 deletions .github/workflows/changeset-report.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Changeset report

on:
push:
branches: [dev]

jobs:
changeset-status:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- uses: pnpm/action-setup@v5
with:
version: 10

- uses: actions/setup-node@v6
with:
node-version: 24.x
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Report pending release changes
run: pnpm changeset status --output changeset-status.json

- uses: actions/upload-artifact@v7
with:
name: changeset-status
path: changeset-status.json
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @tobelabs/chainwright

## 0.10.1

### Patch Changes

- Update READEME.md and fix flakiness in Metamask when onboarding a user

## 0.10.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ These are Chainwright's wallet extensions and their versions:
- **Phantom**: v26.10.0
- **Solflare**: v2.19.1
- **Meteor**: v0.7.0
- **Keplr**: v0.13.3
- **Keplr**: v0.13.39

Example:

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chainwright",
"version": "0.10.0",
"version": "0.10.1",
"description": "Playwright Web3 wallet testing framework for end-to-end dApp automation with MetaMask, Phantom, Solflare, Petra, Meteor, and Keplr",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -94,7 +94,7 @@
"devDependencies": {
"@biomejs/biome": "2.4.15",
"@changesets/cli": "^2.31.0",
"@playwright/test": "^1.60.0",
"@playwright/test": "1.60.0",
"@types/adm-zip": "^0.5.8",
"@types/cli-progress": "^3.11.6",
"@types/node": "^25.8.0",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const WALLET_CONTEXT_DIR_NAME = ".wallet-context";
export const WALLET_SETUP_DIR_NAME = "wallet-setup";

const METAMASK_VERSION = "13.33.0";
const KEPLR_VERSION = "0.13.3";
const KEPLR_VERSION = "0.13.39";

const ARCHIVED_WALLET_BASE_URL = `https://github.com/amaify/chainwright/releases/download/v0.1.0/`;
const KEPLR_WALLET_BASE_URL = `https://github.com/chainapsis/keplr-wallet/releases/download/v${KEPLR_VERSION}/`;
Expand Down Expand Up @@ -47,6 +47,6 @@ export const SUPPORTED_WALLETS: SupportedWalletsMap = {
keplr: {
downloadUrl: KEPLR_DOWNLOAD_URL,
extensionName: "Keplr",
sha256: "df601d806aea6b345ab0fc53504c58ecae1aa0c108d8b3f02dd743e0fcbb0751",
sha256: "15811192edf86074e351c4e4e569ec1282054f316f6d958fa6c5485e23aa47b6",
},
};
14 changes: 8 additions & 6 deletions src/utils/prepare-wallet-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ export async function prepareWalletExtension({ downloadUrl, name, force, extensi
styleText("cyanBright", `📥 Downloading ${walletName} extension...`, { validateStream: false }),
);
await downloadFile({ url: urlSource, destination: zipFilePath });
await verifyFileIntegrity({
filePath: zipFilePath,
expectedSha256,
label: `${walletName} extension`,
});
console.info(
styleText("green", `✅ ${name.toUpperCase()} Extension downloaded successfully.`, {
validateStream: false,
Expand All @@ -84,8 +79,15 @@ export async function prepareWalletExtension({ downloadUrl, name, force, extensi

// Unzip the archive if not already extracted
if (!fs.existsSync(outputPath)) {
const _filePath = !localPath ? zipFilePath : localPath;
console.info(`🚨 Verifying file integrity...`);
await verifyFileIntegrity({
filePath: _filePath,
expectedSha256,
label: `${walletName} extension`,
});
console.info(`📦 Extracting extension...`);
const zip = new AdmZip(!localPath ? zipFilePath : localPath);
const zip = new AdmZip(_filePath);
zip.extractAllTo(outputPath, true);
console.info(`✅ ${walletName} Extension extracted successfully.`);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/wallets/get-popup-page-from-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function getPopupPageFromContext({ context, path, locator }: GetPop
return !!popupPage;
},
{
timeout: 30_000,
timeout: 60_000,
},
)
.toBe(true);
Expand Down
2 changes: 0 additions & 2 deletions src/wallets/keplr/actions/onboard.keplr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { styleText } from "node:util";
import { expect, type Page } from "@playwright/test";
import { sleep } from "@/utils/sleep";
import { homepageSelectors } from "../selectors/homepage-selectors.keplr";
import type { OnboardingArgs } from "../types";
import { addWalletViaPrivateKey, addWalletViaSeedPhrase, goToOnboardingPage } from "../utils";
Expand Down Expand Up @@ -115,6 +114,5 @@ export default async function onboard({ page, onboard }: Onboard) {
}
}

await sleep(3_000);
console.info(styleText("greenBright", "✨ Keplr onboarding completed successfully", { validateStream: false }));
}
13 changes: 3 additions & 10 deletions src/wallets/keplr/keplr-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import persistLocalStorage from "@/utils/persist-local-storage";
import { teardownContext } from "@/utils/teardown-context";
import waitForStablePage from "@/utils/wait-for-stable-page";
import { getWalletExtensionPathFromCache } from "@/utils/wallets/get-wallet-extension-path-from-cache";
import { getBrowserArgs } from "../utils/get-browser-args";
import { unlock } from "./actions/unlock.keplr";
import { Keplr } from "./keplr";
import { KeplrProfile } from "./keplr-profile";
Expand Down Expand Up @@ -36,15 +37,7 @@ export const keplrFixture = ({ slowMo = 0, profileName }: WalletProfileFixtureAr

fs.cpSync(walletDataDir, tempWalletDataDir, { recursive: true, force: true });

const browserArgs = [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`];
if (process.env.HEADLESS) {
browserArgs.push("--headless=new");

if (slowMo > 0) {
console.warn("⚠️ Slow motion makes no sense in headless mode. It will be ignored!");
}
}

const browserArgs = getBrowserArgs(extensionPath, slowMo);
const walletPageContext = await chromium.launchPersistentContext(tempWalletDataDir, {
headless: false,
args: browserArgs,
Expand All @@ -55,7 +48,7 @@ export const keplrFixture = ({ slowMo = 0, profileName }: WalletProfileFixtureAr

const { cookies, origins } = await currentContext.storageState();
if (cookies) await walletPageContext.addCookies(cookies);
if (origins && origins.length > 0) persistLocalStorage(origins, walletPageContext);
if (origins && origins.length > 0) await persistLocalStorage(origins, walletPageContext);

const indexUrl = await wallet.indexUrl();
const homePage = walletPageContext.pages().find((page) => page.url().startsWith(indexUrl));
Expand Down
4 changes: 2 additions & 2 deletions src/wallets/metamask/actions/onboard.metamask.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { styleText } from "node:util";
import { expect, type Page } from "@playwright/test";
import { sleep } from "@/utils/sleep";
import { getWalletPasswordFromCache } from "@/utils/wallets/get-wallet-password-from-cache";
import { MetamaskProfile } from "../metamask-profile";
import { homepageSelectors } from "../selectors/homepage-selectors.metamask";
import { onboardSelectors } from "../selectors/onboard-selectors.metamask";
import type { OnboardingArgs } from "../types";
import { ensureMetaMaskOnboardingCompleted } from "../utils";
import { switchAccount } from "./switch-account.metamask";
import { toggleShowTestnetNetwork } from "./toggle-show-testnet-network.metamask";

Expand Down Expand Up @@ -142,6 +142,6 @@ export default async function onboard({ page, mainAccountName, ...args }: Onboar
await switchAccount(page, mainAccountName);
}

await sleep(5_000);
await ensureMetaMaskOnboardingCompleted(page);
console.info(styleText("greenBright", "✨ MetaMask onboarding completed successfully", { validateStream: false }));
}
4 changes: 3 additions & 1 deletion src/wallets/metamask/actions/switch-account.metamask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ export async function switchAccount(page: Page, accountName: string) {
throw Error(`Account with name "${accountName}" not found.`);
}

await currentAccount?.click();
await currentAccount.click();
const loadingSpinner = page.locator("div[class='spinner loading-overlay__spinner']");
await loadingSpinner.waitFor({ state: "detached" });
}
17 changes: 5 additions & 12 deletions src/wallets/metamask/metamask-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import getPageFromContext from "@/utils/get-page-from-context";
import persistLocalStorage from "@/utils/persist-local-storage";
import { teardownContext } from "@/utils/teardown-context";
import { getWalletExtensionPathFromCache } from "@/utils/wallets/get-wallet-extension-path-from-cache";
import { getBrowserArgs } from "../utils/get-browser-args";
import unlock from "./actions/unlock.metamask";
import { Metamask } from "./metamask";
import { MetamaskProfile } from "./metamask-profile";
Expand All @@ -35,24 +36,16 @@ export const metamaskFixture = ({ slowMo = 0, profileName }: WalletProfileFixtur

fs.cpSync(walletDataDir, tempWalletDataDir, { recursive: true, force: true });

const browserArgs = [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`];
if (process.env.HEADLESS) {
browserArgs.push("--headless=new");

if (slowMo > 0) {
console.warn("⚠️ Slow motion makes no sense in headless mode. It will be ignored!");
}
}

const browserArgs = getBrowserArgs(extensionPath, slowMo);
const walletPageContext = await chromium.launchPersistentContext(tempWalletDataDir, {
headless: false,
args: [`--disable-extensions-except=${extensionPath}`],
args: browserArgs,
slowMo: process.env.HEADLESS ? 0 : slowMo,
});

const { cookies, origins } = await currentContext.storageState();
if (cookies) await walletPageContext.addCookies(cookies);
if (origins && origins.length > 0) persistLocalStorage(origins, walletPageContext);
if (origins && origins.length > 0) await persistLocalStorage(origins, walletPageContext);

const indexUrl = await wallet.indexUrl();
await walletPageContext.waitForEvent("page", {
Expand All @@ -68,7 +61,7 @@ export const metamaskFixture = ({ slowMo = 0, profileName }: WalletProfileFixtur
}

const loadingSpinner = _metamaskPage.locator("img[class='loading-spinner']");
await loadingSpinner.waitFor({ state: "detached" });
await loadingSpinner.waitFor({ state: "detached", timeout: 45_000 });

await unlock(_metamaskPage);
await use(walletPageContext);
Expand Down
1 change: 0 additions & 1 deletion src/wallets/metamask/metamask-worker-scope-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const metamaskWorkerScopeFixture = ({ profileName, slowMo }: WalletProfil
profileName,
slowMo,
});
await context.grantPermissions(["clipboard-read"]);
const metamask = new Metamask(walletPageFromContext);
await metamask.unlock();

Expand Down
52 changes: 51 additions & 1 deletion src/wallets/metamask/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { errors, type Page } from "@playwright/test";
import { errors, expect, type Page } from "@playwright/test";
import { sleep } from "@/utils/sleep";
import waitForStablePage from "@/utils/wait-for-stable-page";
import { loadingSelectors } from "./selectors/loading-selectors.metamask";
Expand Down Expand Up @@ -35,3 +35,53 @@ export const waitForMetaMaskLoad = async (page: Page) => {

return page;
};

export async function ensureMetaMaskOnboardingCompleted(page: Page) {
const extensionUrl = new URL(page.url());
const extensionOrigin = `${extensionUrl.protocol}//${extensionUrl.host}`;
const context = page.context();

const serviceWorker =
context.serviceWorkers().find((worker) => worker.url().startsWith(extensionOrigin)) ??
(await context.waitForEvent("serviceworker", {
predicate: (worker) => worker.url().startsWith(extensionOrigin),
timeout: 60_000,
}));

await expect
.poll(
async () => {
try {
return await serviceWorker.evaluate(
`(async () => {
const state = await chrome.storage.local.get([
"OnboardingController",
"KeyringController",
"PreferencesController",
]);
const record = (value) => value && typeof value === "object" ? value : {};

const onboarding = record(state.OnboardingController);
const keyring = record(state.KeyringController);
const preferencesController = record(state.PreferencesController);
const preferences = record(preferencesController.preferences);

return (
onboarding.completedOnboarding === true &&
typeof keyring.vault === "string" &&
keyring.vault.length > 0 &&
preferences.showTestNetworks === true
);
})()`,
);
} catch {
return false;
}
},
{
message: "MetaMask onboarding state was not persisted to extension storage",
timeout: 60_000,
},
)
.toBe(true);
}
7 changes: 6 additions & 1 deletion src/wallets/metamask/worker-scope-context.metamask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import createTempContextDirectory from "@/utils/create-temp-context-directory";
import getCacheDirectory from "@/utils/get-cache-directory";
import waitForStablePage from "@/utils/wait-for-stable-page";
import { getWalletExtensionPathFromCache } from "@/utils/wallets/get-wallet-extension-path-from-cache";
import { getBrowserArgs } from "../utils/get-browser-args";
import { MetamaskProfile } from "./metamask-profile";

type WorkerScopeContext = {
Expand All @@ -27,9 +28,10 @@ export async function workerScopeContextMetamask({ workerInfo, profileName, slow
fs.cpSync(walletDataDir, contextPath, { recursive: true, force: true });
const walletPath = await getWalletExtensionPathFromCache(wallet.name);

const browserArgs = getBrowserArgs(walletPath, slowMo ?? 0);
const context = await chromium.launchPersistentContext(contextPath, {
headless: false,
args: [`--disable-extensions-except=${walletPath}`],
args: browserArgs,
slowMo: process.env.HEADLESS ? 0 : slowMo,
});

Expand All @@ -52,5 +54,8 @@ export async function workerScopeContextMetamask({ workerInfo, profileName, slow
}
}

const loadingSpinner = page.locator("img[class='loading-spinner']");
await loadingSpinner.waitFor({ state: "detached" });

return { context, walletPage: page, contextPath };
}
1 change: 0 additions & 1 deletion src/wallets/meteor/actions/onboard.meteor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,5 @@ export default async function onboard({ page, network, accountName, additionalAc
await switchAccount(page, accountName);
}

await sleep(3_000);
console.info(styleText("greenBright", "✨ Meteor onboarding completed successfully"));
}
Loading
Loading