diff --git a/.github/agents/playwright-test-generator.agent.md b/.github/agents/playwright-test-generator.agent.md
new file mode 100644
index 00000000..4219564e
--- /dev/null
+++ b/.github/agents/playwright-test-generator.agent.md
@@ -0,0 +1,93 @@
+---
+name: playwright-test-generator
+description: 'Use this agent when you need to create automated browser tests using Playwright Examples: Context: User wants to generate a test for the test plan item. '
+tools:
+ - search
+ - playwright-test/browser_click
+ - playwright-test/browser_drag
+ - playwright-test/browser_evaluate
+ - playwright-test/browser_file_upload
+ - playwright-test/browser_handle_dialog
+ - playwright-test/browser_hover
+ - playwright-test/browser_navigate
+ - playwright-test/browser_press_key
+ - playwright-test/browser_select_option
+ - playwright-test/browser_snapshot
+ - playwright-test/browser_type
+ - playwright-test/browser_verify_element_visible
+ - playwright-test/browser_verify_list_visible
+ - playwright-test/browser_verify_text_visible
+ - playwright-test/browser_verify_value
+ - playwright-test/browser_wait_for
+ - playwright-test/generator_read_log
+ - playwright-test/generator_setup_page
+ - playwright-test/generator_write_test
+model: Claude Sonnet 4
+mcp-servers:
+ playwright-test:
+ type: stdio
+ command: npx
+ args:
+ - playwright
+ - run-test-mcp-server
+ tools:
+ - '*'
+---
+
+You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
+Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
+application behavior.
+
+# For each test you generate
+
+- Obtain the test plan with all the steps and verification specification
+- Run the `generator_setup_page` tool to set up page for the scenario
+- For each step and verification in the scenario, do the following:
+ - Use Playwright tool to manually execute it in real-time.
+ - Use the step description as the intent for each Playwright tool call.
+- Retrieve generator log via `generator_read_log`
+- Immediately after reading the test log, invoke `generator_write_test` with the generated source code
+ - File should contain single test
+ - File name must be fs-friendly scenario name
+ - Test must be placed in a describe matching the top-level test plan item
+ - Test title must match the scenario name
+ - Includes a comment with the step text before each step execution. Do not duplicate comments if step requires
+ multiple actions.
+ - Always use best practices from the log when generating tests.
+
+
+ For following plan:
+
+ ```markdown file=specs/plan.md
+ ### 1. Adding New Todos
+
+ **Seed:** `tests/seed.spec.ts`
+
+ #### 1.1 Add Valid Todo
+
+ **Steps:**
+
+ 1. Click in the "What needs to be done?" input field
+
+ #### 1.2 Add Multiple Todos
+
+ ...
+ ```
+
+ Following file is generated:
+
+ ```ts file=add-valid-todo.spec.ts
+ // spec: specs/plan.md
+ // seed: tests/seed.spec.ts
+
+ test.describe('Adding New Todos', () => {
+ test('Add Valid Todo', async { page } => {
+ // 1. Click in the "What needs to be done?" input field
+ await page.click(...);
+
+ ...
+ });
+ });
+ ```
+
+
diff --git a/.github/agents/playwright-test-healer.agent.md b/.github/agents/playwright-test-healer.agent.md
new file mode 100644
index 00000000..02f713df
--- /dev/null
+++ b/.github/agents/playwright-test-healer.agent.md
@@ -0,0 +1,65 @@
+---
+name: playwright-test-healer
+description: Use this agent when you need to debug and fix failing Playwright tests
+tools:
+ - search
+ - edit
+ - playwright-test/browser_console_messages
+ - playwright-test/browser_evaluate
+ - playwright-test/browser_generate_locator
+ - playwright-test/browser_network_requests
+ - playwright-test/browser_snapshot
+ - playwright-test/test_debug
+ - playwright-test/test_list
+ - playwright-test/test_run
+model: Claude Sonnet 4
+mcp-servers:
+ playwright-test:
+ type: stdio
+ command: npx
+ args:
+ - playwright
+ - run-test-mcp-server
+ tools:
+ - '*'
+---
+
+You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
+resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
+broken Playwright tests using a methodical approach.
+
+Your workflow:
+
+1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
+2. **Debug failed tests**: For each failing test run `test_debug`.
+3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
+ - Examine the error details
+ - Capture page snapshot to understand the context
+ - Analyze selectors, timing issues, or assertion failures
+4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
+ - Element selectors that may have changed
+ - Timing and synchronization issues
+ - Data dependencies or test environment problems
+ - Application changes that broke test assumptions
+5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
+ - Updating selectors to match current application state
+ - Fixing assertions and expected values
+ - Improving test reliability and maintainability
+ - For inherently dynamic data, utilize regular expressions to produce resilient locators
+6. **Verification**: Restart the test after each fix to validate the changes
+7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
+
+Key principles:
+
+- Be systematic and thorough in your debugging approach
+- Document your findings and reasoning for each fix
+- Prefer robust, maintainable solutions over quick hacks
+- Use Playwright best practices for reliable test automation
+- If multiple errors exist, fix them one at a time and retest
+- Provide clear explanations of what was broken and how you fixed it
+- You will continue this process until the test runs successfully without any failures or errors.
+- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
+ so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
+ of the expected behavior.
+- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
+- Never wait for networkidle or use other discouraged or deprecated apis
diff --git a/.github/agents/playwright-test-planner.agent.md b/.github/agents/playwright-test-planner.agent.md
new file mode 100644
index 00000000..7cfc129a
--- /dev/null
+++ b/.github/agents/playwright-test-planner.agent.md
@@ -0,0 +1,81 @@
+---
+name: playwright-test-planner
+description: Use this agent when you need to create comprehensive test plan for a web application or website
+tools:
+ - search
+ - playwright-test/browser_click
+ - playwright-test/browser_close
+ - playwright-test/browser_console_messages
+ - playwright-test/browser_drag
+ - playwright-test/browser_evaluate
+ - playwright-test/browser_file_upload
+ - playwright-test/browser_handle_dialog
+ - playwright-test/browser_hover
+ - playwright-test/browser_navigate
+ - playwright-test/browser_navigate_back
+ - playwright-test/browser_network_requests
+ - playwright-test/browser_press_key
+ - playwright-test/browser_select_option
+ - playwright-test/browser_snapshot
+ - playwright-test/browser_take_screenshot
+ - playwright-test/browser_type
+ - playwright-test/browser_wait_for
+ - playwright-test/planner_setup_page
+ - playwright-test/planner_save_plan
+model: Claude Sonnet 4
+mcp-servers:
+ playwright-test:
+ type: stdio
+ command: npx
+ args:
+ - playwright
+ - run-test-mcp-server
+ tools:
+ - '*'
+---
+
+You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
+scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
+planning.
+
+You will:
+
+1. **Navigate and Explore**
+ - Invoke the `planner_setup_page` tool once to set up page before using any other tools
+ - Explore the browser snapshot
+ - Do not take screenshots unless absolutely necessary
+ - Use `browser_*` tools to navigate and discover interface
+ - Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
+
+2. **Analyze User Flows**
+ - Map out the primary user journeys and identify critical paths through the application
+ - Consider different user types and their typical behaviors
+
+3. **Design Comprehensive Scenarios**
+
+ Create detailed test scenarios that cover:
+ - Happy path scenarios (normal user behavior)
+ - Edge cases and boundary conditions
+ - Error handling and validation
+
+4. **Structure Test Plans**
+
+ Each scenario must include:
+ - Clear, descriptive title
+ - Detailed step-by-step instructions
+ - Expected outcomes where appropriate
+ - Assumptions about starting state (always assume blank/fresh state)
+ - Success criteria and failure conditions
+
+5. **Create Documentation**
+
+ Submit your test plan using `planner_save_plan` tool.
+
+**Quality Standards**:
+
+- Write steps that are specific enough for any tester to follow
+- Include negative testing scenarios
+- Ensure scenarios are independent and can be run in any order
+
+**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
+professional formatting suitable for sharing with development and QA teams.
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 746a97c0..bda8ade2 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,12 +1,19 @@
-name: main-ci
+name: 'CI - Lint, Build & Test'
on:
+ workflow_dispatch:
push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
steps:
- name: Checkout repo
uses: actions/checkout@v4
@@ -20,8 +27,43 @@ jobs:
npm -v
npm ci --no-fund
+ - name: Generate GraphQL types
+ env:
+ VITE_POCO_SUBGRAPH_URL: ${{ secrets.VITE_POCO_SUBGRAPH_URL }}
+ VITE_DATAPROTECTOR_SUBGRAPH_URL: ${{ secrets.VITE_DATAPROTECTOR_SUBGRAPH_URL }}
+ run: npm run codegen
+
- name: Check Prettier
run: npm run check-format
- name: Lint
run: npm run lint
+
+ - name: Build application
+ env:
+ VITE_POCO_SUBGRAPH_URL: ${{ secrets.VITE_POCO_SUBGRAPH_URL }}
+ VITE_DATAPROTECTOR_SUBGRAPH_URL: ${{ secrets.VITE_DATAPROTECTOR_SUBGRAPH_URL }}
+ VITE_REOWN_PROJECT_ID: ${{ secrets.VITE_REOWN_PROJECT_ID }}
+ VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
+ VITE_FAUCET_API_URL: ${{ secrets.VITE_FAUCET_API_URL }}
+ run: npm run build
+
+ - name: Install Playwright Browsers
+ run: npx playwright install --with-deps
+
+ - name: Run Playwright Tests
+ env:
+ VITE_POCO_SUBGRAPH_URL: ${{ secrets.VITE_POCO_SUBGRAPH_URL }}
+ VITE_DATAPROTECTOR_SUBGRAPH_URL: ${{ secrets.VITE_DATAPROTECTOR_SUBGRAPH_URL }}
+ VITE_REOWN_PROJECT_ID: ${{ secrets.VITE_REOWN_PROJECT_ID }}
+ VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }}
+ VITE_FAUCET_API_URL: ${{ secrets.VITE_FAUCET_API_URL }}
+ run: npx playwright test
+
+ - name: Upload Playwright Report
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index d448be5c..66493923 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,11 @@ src/graphql/dataprotector/*
*.njsproj
*.sln
*.sw?
-TODO
\ No newline at end of file
+TODO
+
+# Playwright
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+/playwright/.auth/
diff --git a/package-lock.json b/package-lock.json
index 78cf907d..3cc9c858 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -59,6 +59,7 @@
"@graphql-codegen/cli": "^6.1.0",
"@graphql-codegen/schema-ast": "^5.0.0",
"@parcel/watcher": "^2.5.1",
+ "@playwright/test": "^1.57.0",
"@tanstack/router-plugin": "^1.140.0",
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
"@types/big.js": "^6.2.2",
@@ -3496,6 +3497,22 @@
"lit": "^3"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
+ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.57.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@@ -12863,6 +12880,53 @@
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
"license": "MIT"
},
+ "node_modules/playwright": {
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
+ "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.57.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
+ "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
diff --git a/package.json b/package.json
index 92a33c26..890e610e 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,8 @@
"watch:codegen": "npm run watch:codegen:poco",
"codegen:poco": "graphql-codegen --config codegenPoco.ts",
"codegen:dataprotector": "graphql-codegen --config codegenDataprotector.ts",
- "watch:codegen:poco": "graphql-codegen --config codegenPoco.ts --watch"
+ "watch:codegen:poco": "graphql-codegen --config codegenPoco.ts --watch",
+ "test": "npx playwright test tests/"
},
"dependencies": {
"@clerk/clerk-react": "^5.58.0",
@@ -69,6 +70,7 @@
"@graphql-codegen/cli": "^6.1.0",
"@graphql-codegen/schema-ast": "^5.0.0",
"@parcel/watcher": "^2.5.1",
+ "@playwright/test": "^1.57.0",
"@tanstack/router-plugin": "^1.140.0",
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
"@types/big.js": "^6.2.2",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 00000000..5a31d111
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,79 @@
+import { defineConfig, devices } from '@playwright/test';
+
+/**
+ * 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://localhost: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: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ {
+ name: 'firefox',
+ use: { ...devices['Desktop Firefox'] },
+ },
+
+ {
+ name: 'webkit',
+ use: { ...devices['Desktop Safari'] },
+ },
+
+ /* 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' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:5173',
+ reuseExistingServer: true,
+ },
+});
diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx
index d87615b3..08f3f497 100644
--- a/src/components/CopyButton.tsx
+++ b/src/components/CopyButton.tsx
@@ -65,6 +65,9 @@ const CopyButton = ({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className="hover:before:bg-muted active:before:bg-secondary relative z-0 -mx-1 -my-1 flex items-center gap-1 px-1 py-1 transition-colors before:absolute before:inset-0 before:-z-10 before:rounded-lg before:duration-200 active:before:scale-x-[0.98] active:before:scale-y-[0.94]"
+ id="copy-button"
+ type="button"
+ aria-label="Copy"
>
{buttonText && {buttonText}}
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 246e99b0..02e20bf2 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -15,22 +15,40 @@ import { Button } from './ui/button';
function SocialLinksItems({ className }: { className?: string }) {
const socialLinks = [
- { href: 'https://twitter.com/iEx_ec', icon: },
- { href: 'https://discord.gg/pbt9m98wnU', icon: },
- { href: 'https://t.me/iexec_rlc_official', icon: },
+ {
+ href: 'https://twitter.com/iEx_ec',
+ icon: ,
+ ariaLabel: 'Twitter',
+ },
+ {
+ href: 'https://discord.gg/pbt9m98wnU',
+ icon: ,
+ ariaLabel: 'Discord',
+ },
+ {
+ href: 'https://t.me/iexec_rlc_official',
+ icon: ,
+ ariaLabel: 'Telegram',
+ },
{
href: 'https://www.youtube.com/channel/UCwWxZWvKVHn3CXnmDooLWtA',
icon: ,
+ ariaLabel: 'YouTube',
},
{
href: 'https://www.linkedin.com/company/iex.ec/',
icon: ,
+ ariaLabel: 'LinkedIn',
+ },
+ {
+ href: 'https://medium.com/iex-ec',
+ icon: ,
+ ariaLabel: 'Medium',
},
- { href: 'https://medium.com/iex-ec', icon: },
];
return (
- {socialLinks.map(({ href, icon }, idx) => (
+ {socialLinks.map(({ href, icon, ariaLabel }, idx) => (