Skip to content

Commit

Permalink
Merge pull request #59 from openfort-xyz/feat/add-e2e-tests
Browse files Browse the repository at this point in the history
Feat/add e2e tests
  • Loading branch information
jamalavedra authored Jan 9, 2025
2 parents 3ff949c + 6fc57e4 commit 8169dc7
Show file tree
Hide file tree
Showing 19 changed files with 938 additions and 82 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Playwright Tests
on:
pull_request:
branches:
- main
merge_group:
branches:
- main
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest

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

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version-file: .nvmrc
cache: 'yarn'

- name: Install root dependencies
run: yarn install --immutable

- name: Install auth-sample dependencies
run: yarn install --immutable
working-directory: examples/apps/auth-sample

- name: Lint
run: yarn lint

- name: Build
run: yarn build

- name: Install Playwright Browsers
run: yarn playwright install --with-deps
working-directory: examples/apps/auth-sample

- name: Run Playwright tests
env:
NEXTAUTH_OPENFORT_SECRET_KEY: ${{ secrets.NEXTAUTH_OPENFORT_SECRET_KEY }}
NEXTAUTH_SHIELD_ENCRYPTION_SHARE: ${{ secrets.NEXTAUTH_SHIELD_ENCRYPTION_SHARE }}
NEXTAUTH_SHIELD_SECRET_KEY: ${{ secrets.NEXTAUTH_SHIELD_SECRET_KEY }}
E2E_TESTS_PASSWORD: ${{ secrets.E2E_TESTS_PASSWORD }}
NEXT_PUBLIC_OPENFORT_PUBLIC_KEY: ${{ vars.NEXT_PUBLIC_OPENFORT_PUBLIC_KEY }}
NEXT_PUBLIC_SHIELD_API_KEY: ${{ vars.NEXT_PUBLIC_SHIELD_API_KEY }}
NEXT_PUBLIC_POLICY_ID: ${{ vars.NEXT_PUBLIC_POLICY_ID }}
NEXT_PUBLIC_CHAIN_ID: ${{ vars.NEXT_PUBLIC_CHAIN_ID }}
NEXT_PUBLIC_CONTRACT_ID: ${{ vars.NEXT_PUBLIC_CONTRACT_ID }}
NEXT_PUBLIC_REOWN_PROJECT_ID: ${{ vars.NEXT_PUBLIC_REOWN_PROJECT_ID }}
NEXT_PUBLIC_COINBASE_PROJECTID: ${{ vars.NEXT_PUBLIC_COINBASE_PROJECTID }}
NEXT_PUBLIC_MOONPAY_API_KEY: ${{ vars.NEXT_PUBLIC_MOONPAY_API_KEY }}
E2E_TESTS_USER: ${{ vars.E2E_TESTS_USER }}
run: yarn playwright test --reporter=dot --workers=5
working-directory: examples/apps/auth-sample

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: examples/apps/auth-sample/playwright-report/
retention-days: 30
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ dist/
yalc.lock

docs/

# Test and Playwright related artifacts
/examples/apps/auth-sample/test-results/
/examples/apps/auth-sample/playwright-report/
/examples/apps/auth-sample/blob-report/
/examples/apps/auth-sample/playwright/.cache/
/examples/apps/auth-sample/playwright/.auth/
4 changes: 4 additions & 0 deletions examples/apps/auth-sample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"ethers": "5.7.2",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.454.0",
"next": "^12.3.1",
Expand All @@ -38,8 +40,10 @@
"wagmi": "^2.13.0"
},
"devDependencies": {
"@playwright/test": "^1.48.0",
"@types/cors": "^2.8.17",
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.7.5",
"@types/react": "^18.3.0",
"autoprefixer": "^10.3.4",
"lint-staged": "^13.0.3",
Expand Down
103 changes: 103 additions & 0 deletions examples/apps/auth-sample/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { defineConfig, devices } from '@playwright/test';

import dotenv from 'dotenv';
import path from 'path';

// Alternatively, read from "../my.env" file.
dotenv.config({ path: path.resolve(__dirname, '.env.local') });

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
// Setup project
{ name: 'setup', testMatch: /.*\.setup\.ts/ },

{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

{
name: 'webkit',
use: {
...devices['Desktop Safari'],
// Use prepared auth state.
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
// outputDir: "playwright/.auth/",

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const BackendMintButton: React.FC<{
className='w-full'
onClick={handleMintNFT}
disabled={!sessionKey}
id="mint-nft-button"
variant="outline"
>
{loading ? <Loading /> : 'Mint NFT with session key'}
Expand Down
2 changes: 1 addition & 1 deletion examples/apps/auth-sample/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const HomePage: NextPage = () => {
});
if (sessionData) {
setUser(sessionData);
handleSetMessage(JSON.stringify(sessionData, null, 2));
setMessage((prev) => !prev.includes("User:") ? `> User: ${JSON.stringify(sessionData, null, 2)}\n\n${prev}` : prev);
} else router.push("/login");
};
if (!user) fetchUser();
Expand Down
57 changes: 57 additions & 0 deletions examples/apps/auth-sample/tests/Logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { expect, Locator, Page } from "@playwright/test";

export class Logger {
page: Page
textArea: Locator | null = null
logs: string[] = []

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

async init() {
await this.page.waitForSelector('textarea', { timeout: 5000 });
this.textArea = this.page.locator("textarea").first()

await this.textArea.textContent()

await expect(this.textArea).toHaveValue(/.*> User:/, { timeout: 10000 })

this.logs.push(await this.textArea.inputValue())
}

getLastLog() {
return this.logs[this.logs.length - 1]
}


async waitForNewLogs(options: { pollInterval?: number, timeout?: number } = {}) {
const { pollInterval = 1000, timeout = 30000 } = options;

if (!this.textArea) {
throw new Error("Logger not initialized");
}

const timer = setTimeout(() => {
throw new Error("Timeout waiting for new logs")
}, timeout);

// Store the initial value
let currentValue = await this.textArea.inputValue();

// Polling loop: keep checking until the value changes
while (currentValue === await this.textArea.inputValue()) {
// Wait for the specified interval before checking again
await new Promise(resolve => setTimeout(resolve, pollInterval));
}

clearTimeout(timer);

const newLogs = (await this.textArea.inputValue()).replace(currentValue, '')

this.logs.push(newLogs)

return newLogs;
}

}
33 changes: 33 additions & 0 deletions examples/apps/auth-sample/tests/accountActions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import test, { expect } from '@playwright/test';
import { Logger } from './Logger';
import { TEST_MINT_SUCCESS_TEXT } from './constants';

test('mint NFT', async ({ page }) => {
await page.goto('/')

const logger = new Logger(page)
await logger.init()

const button = page.getByRole('button', { name: 'Mint NFT' }).first()
button.click()

await logger.waitForNewLogs()

const lastLog = logger.getLastLog()
expect(lastLog).toContain(TEST_MINT_SUCCESS_TEXT)
})

test('Send batch calls', async ({ page }) => {
await page.goto('/')

const logger = new Logger(page)
await logger.init()

const button = page.getByRole('button', { name: 'Send batch calls' }).first()
button.click()

await logger.waitForNewLogs()

const lastLog = logger.getLastLog()
expect(lastLog).toContain(TEST_MINT_SUCCESS_TEXT)
})
62 changes: 62 additions & 0 deletions examples/apps/auth-sample/tests/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { test as setup, expect } from '@playwright/test';
import path from 'path';
import { Logger } from './Logger';

const authFile = path.join(__dirname, '../playwright/.auth/user.json');

setup('authenticate', async ({ page }) => {
// Perform authentication steps. Replace these actions with your own.
await page.goto('/login');

// The new page should contain an h1 with "Sign in to account"
await expect(page.locator('h1')).toContainText('Sign in to account')

await page.getByLabel('Email address').fill(process.env.E2E_TESTS_USER || "");
await page.getByLabel('Password').fill(process.env.E2E_TESTS_PASSWORD || "");

await page.getByRole('button', { name: 'Sign in' }).click();

await page.waitForURL('/');

await expect(page.locator('h1')).toContainText('Set up your embedded signer')

await page.getByRole('button', { name: 'Continue with Automatic Recovery' }).click();

await expect(page.locator('div.spinner')).toBeInViewport();
await page.locator("div.spinner").waitFor({ state: 'hidden' });

const consoleExists = await page.locator('h2').getByText('Console').count() > 0
if (!consoleExists) {
// if console doesn't exists we must be at the login page, so maybe we have to log in with wallet recovery instead
await expect(page.locator('h1')).toContainText('Set up your embedded signer')

const passwordRecoveryInputLogin = page.locator('input[name="passwordRecovery"]')
const passwordRecoveryButtonLogin = page.getByRole('button', { name: 'Continue with Password Recovery' }).first()

await passwordRecoveryInputLogin.fill('password')
passwordRecoveryButtonLogin.click()

await expect(page.locator('div.spinner')).toBeInViewport();
await page.locator("div.spinner").waitFor({ state: 'hidden' });

// we should be logged now
await expect(page.locator('h2').getByText('Console')).toBeVisible()

const logger = new Logger(page)
await logger.init()


const automaticRecoveryButton = page.getByRole('button', { name: 'Set Automatic Recovery' }).first()
const oldPasswordInput = page.locator('input[name="automatic-passwordRecovery"]')

await oldPasswordInput.fill('password')
automaticRecoveryButton.click()

await logger.waitForNewLogs()
const lastLog = logger.getLastLog()

expect(lastLog).toContain("success")
}

await page.context().storageState({ path: authFile });
});
16 changes: 16 additions & 0 deletions examples/apps/auth-sample/tests/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import test, { expect } from '@playwright/test';

// remove default auth tokens
test.use({ storageState: { cookies: [], origins: [] } });

// TODO: Google login
test.skip('Login page', async ({ page }) => {
await page.goto('/')

await expect(page.locator('h1')).toContainText('Sign in to account')

const button = page.locator('span').getByText('Continue with Google').first()
await button.click()

// ...
})
Loading

0 comments on commit 8169dc7

Please sign in to comment.