Skip to content

Commit

Permalink
Setup E2E testing with Playwright (#401)
Browse files Browse the repository at this point in the history
* Setup Playwright

* Add test for signup success flow

* Upgrade playwright to v1.46.0

* Extract webauthn emulator logic as class

* Rename test script to e2e

* Extract keychain connection logic

* Describe e2e test script in readme
  • Loading branch information
JunichiSugiura authored Aug 8, 2024
1 parent 2c64486 commit b18a085
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 46 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# name: End-to-End tests
# on:
# push:
# branches: [main]
# pull_request:
# branches: [main]
# jobs:
# test:
# timeout-minutes: 60
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-node@v4
# with:
# node-version: lts/*
# - name: Install dependencies
# run: npm install -g pnpm && pnpm install
# - name: Install Playwright Browsers
# run: pnpm exec playwright install --with-deps
# - name: Run Playwright tests
# run: pnpm exec playwright test
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: playwright-report/
# retention-days: 30
5 changes: 5 additions & 0 deletions examples/starknet-react-next/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
54 changes: 18 additions & 36 deletions examples/starknet-react-next/README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,27 @@
This is an example showing how to use StarkNet React with Next.js
## Controller Example (Next.js)

## Getting Started
### Development

First, run the development server:

```bash
pnpm example:next dev
```
pnpm i
pnpm dev
Open [http://localhost:3000](http://localhost:3000) with your browser to see the
result.

You can start editing the page by modifying `pages/index.tsx`. The page
auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on
[http://localhost:3000/api/hello](http://localhost:3000/api/hello). This
endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are
treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead
of React pages.

## Learn More

To learn more about Next.js, take a look at the following resources:
# Example page: localhost:3002
# Keychain: localhost:3001
```

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
### E2E test

You can check out
[the Next.js GitHub repository](https://github.com/vercel/next.js/) - your
feedback and contributions are welcome!
```sh
# Start api server locally
bin/apidev

## Deploy on Vercel
# Start example page and keychain
pnpm dev

The easiest way to deploy your Next.js app is to use the
[Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme)
from the creators of Next.js.
# Run test
pnpm example:next e2e

Check out our
[Next.js deployment documentation](https://nextjs.org/docs/deployment) for more
details.
# or Run test with Playwright UI
pnpm example:next e2e:ui
```
1 change: 0 additions & 1 deletion examples/starknet-react-next/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
const nextConfig = {
productionBrowserSourceMaps: true,
reactStrictMode: true,
swcMinify: false,
experimental: {
externalDir: true,
},
Expand Down
3 changes: 3 additions & 0 deletions examples/starknet-react-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"scripts": {
"dev": "next dev -p 3002",
"build": "next build",
"e2e": "playwright test",
"e2e:ui": "playwright test --ui",
"start": "next start -p 3002",
"lint": "next lint",
"format": "prettier --write ."
Expand All @@ -23,6 +25,7 @@
},
"devDependencies": {
"@cartridge/tsconfig": "workspace:^",
"@playwright/test": "^1.46.0",
"@types/node": "^20.6.0",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
Expand Down
36 changes: 36 additions & 0 deletions examples/starknet-react-next/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "./tests",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
baseURL: "http://localhost:3002",
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: "firefox",
// use: { ...devices["Desktop Firefox"] },
// },
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
],
webServer: {
command: "pnpm dev",
cwd: "../..",
port: 3002,
reuseExistingServer: !process.env.CI,
stdout: "pipe",
stderr: "pipe",
},
});
2 changes: 1 addition & 1 deletion examples/starknet-react-next/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Config } from "tailwindcss";
const config = {
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@cartridge/ui-next/**/*.{js,jsx}",
"./node_modules/@cartridge/ui-next/dist/**/*.{js,jsx}",
],
presets: [cartridgeTWPreset],
} satisfies Config;
Expand Down
24 changes: 24 additions & 0 deletions examples/starknet-react-next/tests/connect.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test, expect } from "@playwright/test";
import { WebauthnEmulator } from "./webauthn";
import { Keychain } from "./keychain";

test.beforeEach(async ({ page }) => {
await page.goto("/");

const client = await page.context().newCDPSession(page);
const webauthn = new WebauthnEmulator({ client });

await webauthn.enable();
await webauthn.addVirtualAuthenticator();
});

test("Sign up -> Disconnect -> Log in", async ({ page }) => {
const keychain = new Keychain({ page });

await keychain.signup();
await keychain.session();
await keychain.disconnect();
await keychain.login();

await expect(page.getByText(`Username: ${keychain.username}`)).toBeVisible();
});
43 changes: 43 additions & 0 deletions examples/starknet-react-next/tests/keychain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { FrameLocator } from "@playwright/test";
import { Page } from "@playwright/test";

export class Keychain {
private page: Page;
private modal: FrameLocator;
public username: string;

constructor({ page, username }: { page: Page; username?: string }) {
this.page = page;
this.modal = page.frameLocator("#cartridge-modal");
this.username = username ?? this.randomUsername();
}

async signup() {
await this.connect();
await this.modal.getByPlaceholder("Username").fill(this.username);
await this.modal.getByText("SIGN UP").click();
}

async session() {
await this.modal.getByRole("button", { name: "CREATE SESSION" }).click();
}

async login() {
await this.connect();
await this.modal.getByText("Log In").click();
await this.modal.getByPlaceholder("Username").fill(this.username);
await this.modal.getByText("LOG IN").click();
}

disconnect() {
return this.page.getByText("Disconnect").click();
}

private connect() {
return this.page.getByText("Connect").click();
}

private randomUsername() {
return `test-${Math.random().toString().slice(2, -1)}`;
}
}
9 changes: 9 additions & 0 deletions examples/starknet-react-next/tests/metadata.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test, expect } from "@playwright/test";

test.beforeEach(async ({ page }) => {
await page.goto("/");
});

test("Page Title", async ({ page }) => {
await expect(page).toHaveTitle(/StarkNet ❤️ React/);
});
42 changes: 42 additions & 0 deletions examples/starknet-react-next/tests/webauthn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { CDPSession } from "@playwright/test";

export class WebauthnEmulator {
private client: CDPSession;
public authenticatorId?: string;

constructor({ client }: { client: CDPSession }) {
this.client = client;
}

async enable() {
await this.client.send("WebAuthn.enable");
}

async addVirtualAuthenticator() {
const { authenticatorId } = await this.client.send(
"WebAuthn.addVirtualAuthenticator",
{
options: {
protocol: "ctap2",
transport: "ble",
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true,
automaticPresenceSimulation: true,
},
},
);

this.authenticatorId = authenticatorId;
}

async removeVirtualAuthenticator() {
if (!this.authenticatorId) {
throw new Error("No virtual authnticator found");
}

await this.client.send("WebAuthn.removeVirtualAuthenticator", {
authenticatorId: this.authenticatorId,
});
}
}
1 change: 0 additions & 1 deletion packages/keychain/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
const nextConfig = {
productionBrowserSourceMaps: true,
reactStrictMode: true,
swcMinify: false,
experimental: {
externalDir: true,
},
Expand Down
3 changes: 1 addition & 2 deletions packages/keychain/src/components/connect/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export function Signup({
<Content>
<Field
{...usernameField}
placeholder="Username"
autoFocus
onChange={(e) => {
setError(undefined);
Expand All @@ -174,8 +175,6 @@ export function Signup({
value: e.target.value.toLowerCase(),
}));
}}
placeholder="Username"
error={usernameField.error}
isLoading={isValidating}
isDisabled={isRegistering}
onClear={() => {
Expand Down
Loading

0 comments on commit b18a085

Please sign in to comment.