Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5471896
feat(frontend-main): add e2e testing with Keplr integration
luciorubeens Nov 15, 2025
4b0a05d
feat: ci test
luciorubeens Nov 17, 2025
ea9fc88
feat: e2e workflow
luciorubeens Nov 17, 2025
14d197d
fix: install playwright
luciorubeens Nov 17, 2025
a9120d0
ci: cache extension
luciorubeens Nov 17, 2025
e7f0049
ci: headless test
luciorubeens Nov 17, 2025
1cd9d39
ci: headed test
luciorubeens Nov 17, 2025
26ee03c
ci: xvfb-run
luciorubeens Nov 17, 2025
c783bcb
ci: no-channel
luciorubeens Nov 17, 2025
d4eedd9
ci: start xvfb
luciorubeens Nov 17, 2025
71a672c
fix: docker container
luciorubeens Nov 17, 2025
61bd4a1
ci: pnpm install
luciorubeens Nov 17, 2025
73c1c34
ci: xvfb-run
luciorubeens Nov 17, 2025
a5980c0
ci: run xvfb
luciorubeens Nov 17, 2025
4c02ecf
ci: test with container
luciorubeens Nov 17, 2025
c572427
ci: headless
luciorubeens Nov 17, 2025
788a2ed
ci: headed
luciorubeens Nov 17, 2025
743af2d
ci: vnc image
luciorubeens Nov 17, 2025
e46ece8
ci: remove xvfb
luciorubeens Nov 17, 2025
121f14d
ci: remove browser installation
luciorubeens Nov 17, 2025
2fd6592
ci: xvfb
luciorubeens Nov 17, 2025
5097879
ci: xvfb-run
luciorubeens Nov 17, 2025
3053f25
ci: -e arg
luciorubeens Nov 17, 2025
49f09dc
wip
luciorubeens Nov 17, 2025
fc06d41
wip
luciorubeens Nov 17, 2025
7fffa62
wip
luciorubeens Nov 18, 2025
9098cae
refactor: headless mode
luciorubeens Nov 18, 2025
0be18f2
fix: slow down
luciorubeens Nov 18, 2025
370cca7
fix: csp
luciorubeens Nov 18, 2025
005d09e
fix: env variables
luciorubeens Nov 18, 2025
f5f2d2f
fix: approve suggest chain
luciorubeens Nov 18, 2025
348d868
Merge remote-tracking branch 'origin/main' into feat/e2e-with-keplr
luciorubeens Nov 19, 2025
5dd2034
fix: remove interpolation
luciorubeens Nov 19, 2025
c731dd3
fix: remove pnpm letfover
luciorubeens Nov 19, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/frontend-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3

- name: Detect api-main changes
- name: Detect frontend-main changes
uses: dorny/paths-filter@v3
id: changes
with:
Expand Down
92 changes: 92 additions & 0 deletions .github/workflows/frontend-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Frontend E2E Tests

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.changes.outputs.frontend-main }}
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Detect frontend-main changes
uses: dorny/paths-filter@v3
id: changes
with:
filters: |
frontend-main:
- 'packages/frontend-main/**'

e2e-tests:
needs: detect-changes
if: needs.detect-changes.outputs.changed == 'true'
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.2

- name: Install dependencies
run: bun install

- name: Store Playwright's Version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(npx playwright --version | sed 's/Version //')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "version=${PLAYWRIGHT_VERSION}" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ steps.playwright-version.outputs.version }}-${{ runner.os }}-playwright-browsers

- name: Cache Playwright user data
uses: actions/cache@v4
with:
path: ./packages/frontend-main/playwright/.user-data
key: playwright-user-data-${{ hashFiles('packages/frontend-main/e2e/setup/*.ts', 'packages/frontend-main/e2e/config/keplr.ts') }}

- name: Cache Keplr extension
uses: actions/cache@v4
with:
path: ./packages/frontend-main/playwright/.cache
key: playwright-cache-${{ hashFiles('packages/frontend-main/e2e/config/keplr.ts') }}

- name: Install Playwright
run: npx playwright install --with-deps chromium

- name: Run Playwright Tests
working-directory: packages/frontend-main
run: bun test:e2e
env:
VITE_ENVIRONMENT_TYPE: testnet
VITE_API_ROOT_TESTNET: https://api.testnet.dither.chat/v1
VITE_COMMUNITY_WALLET_TESTNET: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep
VITE_AUTHZ_GRANTEE_TESTNET: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep

- name: Upload playwright results
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-results
path: |
packages/frontend-main/test-results/
packages/frontend-main/playwright-report/
retention-days: 1
if-no-files-found: ignore
10 changes: 10 additions & 0 deletions bun.lock

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"devDependencies": {
"@antfu/eslint-config": "^6.0.0",
"@playwright/test": "^1.56.1",
"@types/deno": "^2.5.0",
"@types/node": "^22.18.10",
"eslint": "^9.37.0",
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend-main/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
/playwright/.user-data/
10 changes: 10 additions & 0 deletions packages/frontend-main/e2e/config/keplr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const keplrData = {
account: {
seed: `source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast`,
name: 'Test',
password: '!Dither12345678',
},
extensionPath: './playwright/.cache/keplr-wallet-extension',
repository: 'https://github.com/chainapsis/keplr-wallet',
version: 'v0.12.287',
};
45 changes: 45 additions & 0 deletions packages/frontend-main/e2e/fixtures/base.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { mergeTests, test } from '@playwright/test';

import { testWithDither } from './dither.fixture';
import { testWithKeplr } from './keplr.fixture';

const base = mergeTests(testWithKeplr, testWithDither);

export const baseTest = base.extend<{
connectWallet: () => Promise<void>;
}>({
connectWallet: async ({ homePage, keplrPopup }, use) => {
await use(async () => {
await test.step('Navigate to home page', async () => {
await homePage.page.waitForTimeout(1500);
await homePage.navigate();
});

await test.step('Open wallet connection dialog', async () => {
const walletDialog = await homePage.openConnectWalletDialog();
await walletDialog.selectKeplrWallet();
});

await test.step('Unlock Keplr wallet', async () => {
await keplrPopup.invoke(async (k) => {
await k.unlockWalletIfNeeded();
await k.approveSuggestChainIfNeeded();
});
});

let connApproved = false;
await test.step('Approve connection or signature', async () => {
await keplrPopup.invoke(async (k) => {
connApproved = await k.approveConnectionIfNeeded();
await k.approveSignatureIfNeeded();
});
});

if (connApproved) {
await test.step('Approve signature', async () => {
await keplrPopup.invoke(k => k.approveSignatureIfNeeded());
});
}
});
},
});
11 changes: 11 additions & 0 deletions packages/frontend-main/e2e/fixtures/dither.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test as base } from '@playwright/test';

import { HomePage } from '../pom/home.pom';

export const testWithDither = base.extend<{
homePage: HomePage;
}>({
homePage: async ({ page }, use) => {
await use(new HomePage(page));
},
});
76 changes: 76 additions & 0 deletions packages/frontend-main/e2e/fixtures/keplr.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { BrowserContext } from '@playwright/test';

import { test as base, chromium } from '@playwright/test';

import { keplrData } from '../config/keplr';
import { KeplrExtensionPage } from '../pom/keplr-extension.pom';

export interface KeplrPopupAPI {
popup: () => Promise<KeplrExtensionPage>;
invoke: (callback: (page: KeplrExtensionPage) => Promise<any>) => Promise<void>;
}

export const testWithKeplr = base.extend<{
context: BrowserContext;
extensionId: string;
keplrPopup: KeplrPopupAPI;
keplrExtension: KeplrExtensionPage;
}>({
context: async ({ launchOptions }, use) => {
const userDataDir = launchOptions.env?.userDataDir ?? '';
const args = [
`--disable-web-security`,
'--disable-setuid-sandbox',
`--disable-extensions-except=${keplrData.extensionPath}`,
`--load-extension=${keplrData.extensionPath}`,
];

const context = await chromium.launchPersistentContext(userDataDir, {
recordVideo: {
dir: 'test-results/videos',
},
devtools: false,
serviceWorkers: 'allow',
args,
});

await use(context);
await context.close();
},

extensionId: async ({ context }, use) => {
let [background] = context.serviceWorkers();

if (!background) background = await context.waitForEvent('serviceworker');

const extensionId = background.url().split('/')[2];
await use(extensionId);
},

keplrPopup: async ({ context, extensionId }, use) => {
const openPopup = () => KeplrExtensionPage.popupFromContext(context, extensionId);

const invoke = async (callback: (page: KeplrExtensionPage) => Promise<void>): Promise<void> => {
let closed = false;
const keplrPage = await openPopup();
keplrPage.page.on('close', () => {
closed = true;
});

await callback(keplrPage);
await new Promise(r => setTimeout(r, 500));

if (!closed) {
await keplrPage.page.close();
}
};

await use({ popup: openPopup, invoke });
},

keplrExtension: async ({ context, extensionId }, use) => {
const page = await context.newPage();
const keplrPage = new KeplrExtensionPage(page, extensionId);
await use(keplrPage);
},
});
16 changes: 16 additions & 0 deletions packages/frontend-main/e2e/pom/connect-wallet-dialog.pom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Locator, Page } from '@playwright/test';

export class ConnectWalletDialogPage {
readonly page: Page;
readonly modalLocator: Locator;

constructor(page: Page) {
this.page = page;
this.modalLocator = this.page.locator('[role=dialog]:has-text("Connect Wallet")');
}

async selectKeplrWallet() {
await this.modalLocator.waitFor({ state: 'visible' });
await this.page.getByRole('button', { name: 'Keplr Wallet' }).click();
}
}
25 changes: 25 additions & 0 deletions packages/frontend-main/e2e/pom/home.pom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Page } from '@playwright/test';

import { ConnectWalletDialogPage } from './connect-wallet-dialog.pom';

export class HomePage {
readonly page: Page;

constructor(page: Page) {
this.page = page;
}

async navigate() {
await this.page.goto('/');
await this.page.waitForLoadState();
}

async openNewPostDialog() {
await this.page.getByRole('button', { name: 'New post' }).click();
}

async openConnectWalletDialog() {
await this.page.getByRole('button', { name: 'Connect Wallet' }).click();
return new ConnectWalletDialogPage(this.page);
}
}
Loading
Loading