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 e17ba80d..42374032 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",
@@ -3512,6 +3513,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",
@@ -13188,6 +13205,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 3dec90ec..fd2ee132 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/tests/unlogged/display-main-tables-homepage.spec.ts b/tests/unlogged/display-main-tables-homepage.spec.ts
new file mode 100644
index 00000000..a214fbc5
--- /dev/null
+++ b/tests/unlogged/display-main-tables-homepage.spec.ts
@@ -0,0 +1,46 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index', () => {
+ test('Display of main tables on homepage', async ({ page }) => {
+ // Given the user navigates to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Then 5 tables are displayed
+ await expect(page.getByText('Latest deals')).toBeVisible();
+ await expect(page.getByText('Latest tasks')).toBeVisible();
+ await expect(page.getByText('Most pertinent apps')).toBeVisible();
+ await expect(page.getByText('Latest datasets deployed')).toBeVisible();
+ await expect(page.getByText('Most pertinent workerpools')).toBeVisible();
+
+ // And the Deals table contains all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Deal' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'App' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Workerpool' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Dataset' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Time' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Success' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Price' })).toBeVisible();
+
+ // And the Tasks table contains all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Task' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Deadline' })).toBeVisible();
+
+ // And the Apps table contains all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Address' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Name' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Owner' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'TxHash' }).first()).toBeVisible();
+
+ // And the Datasets table contains all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Dataset' }).first()).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Type' })).toBeVisible();
+
+ // And the Workerpools table contains all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Workerpool' }).nth(1)).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Description' })).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/invalid-search.spec.ts b/tests/unlogged/invalid-search.spec.ts
new file mode 100644
index 00000000..4fcae8a9
--- /dev/null
+++ b/tests/unlogged/invalid-search.spec.ts
@@ -0,0 +1,20 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index - Search', () => {
+ test('Invalid search', async ({ page }) => {
+ // Navigate to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Given the user enters `0x908ab1ca1fb0179253534d8b5f7777b8499b34f`
+ await page.getByRole('searchbox', { name: 'Search for addresses, deal' }).fill('0x908ab1ca1fb0179253534d8b5f7777b8499b34f');
+
+ // When the search is executed
+ await page.keyboard.press('Enter');
+
+ // Then no result is returned
+ await expect(page.getByText('Invalid value')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-apps-table-behavior.spec.ts b/tests/unlogged/page-apps-table-behavior.spec.ts
new file mode 100644
index 00000000..d96123a1
--- /dev/null
+++ b/tests/unlogged/page-apps-table-behavior.spec.ts
@@ -0,0 +1,27 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Page Apps', () => {
+ test('Apps table behavior', async ({ page }) => {
+ // Given the user navigates to the Apps page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/apps');
+
+ // Then the table is displayed with all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Address' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Name' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Owner' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Time' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'TxHash' })).toBeVisible();
+
+ // And pagination is available
+ await expect(page.getByText('Previous')).toBeVisible();
+ await expect(page.getByText('Next')).toBeVisible();
+
+ // And the Home button is present and functional
+ await expect(page.getByText('Homepage')).toBeVisible();
+ await page.getByRole('link', { name: 'Homepage' }).click();
+ await expect(page.getByText('The iExec Protocol Explorer')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-datasets-table-behavior.spec.ts b/tests/unlogged/page-datasets-table-behavior.spec.ts
new file mode 100644
index 00000000..8b168aa4
--- /dev/null
+++ b/tests/unlogged/page-datasets-table-behavior.spec.ts
@@ -0,0 +1,28 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Page Datasets', () => {
+ test('Datasets table behavior', async ({ page }) => {
+ // Given the user navigates to the Datasets page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/datasets');
+
+ // Then the table is displayed with all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Dataset' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Name' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Type' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Owner' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Time' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'TxHash' })).toBeVisible();
+
+ // And pagination is available
+ await expect(page.getByText('Previous')).toBeVisible();
+ await expect(page.getByText('Next')).toBeVisible();
+
+ // And the Home button is present and functional
+ await expect(page.getByText('Homepage')).toBeVisible();
+ await page.getByRole('link', { name: 'Homepage' }).click();
+ await expect(page.getByText('The iExec Protocol Explorer')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-deal-behavior.spec.ts b/tests/unlogged/page-deal-behavior.spec.ts
new file mode 100644
index 00000000..7dd16b67
--- /dev/null
+++ b/tests/unlogged/page-deal-behavior.spec.ts
@@ -0,0 +1,20 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Deal Details Page', () => {
+ test('Verify deal tabs', async ({ page }) => {
+ // Navigate directly to a deal details page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/deal/0x0481dbad29b7863d88a14abb5ed2d57260f464362effd2b063b6970ff3e5b040');
+
+ // Verify that the DETAILS tab is visible
+ await expect(page.getByRole('tab', { name: 'DETAILS' })).toBeVisible();
+
+ // Verify that the TASKS tab is visible
+ await expect(page.getByRole('tab', { name: 'TASKS' })).toBeVisible();
+
+ // Verify that the ASSOCIATED DEALS tab is visible
+ await expect(page.getByRole('tab', { name: 'ASSOCIATED DEALS' })).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-deals-table-behavior.spec.ts b/tests/unlogged/page-deals-table-behavior.spec.ts
new file mode 100644
index 00000000..7b2ec9eb
--- /dev/null
+++ b/tests/unlogged/page-deals-table-behavior.spec.ts
@@ -0,0 +1,37 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Page Deals', () => {
+ test('Deals table behavior', async ({ page }) => {
+ // Given the user navigates to the Deals page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/deals');
+
+ // Then the table is displayed with all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Deal' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'App' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Workerpool' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Dataset' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Time' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Success' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Price' })).toBeVisible();
+
+ // And pagination is available
+ await expect(page.getByText('Previous')).toBeVisible();
+ await expect(page.getByText('Next')).toBeVisible();
+
+ // And the Home button is present and functional
+ await expect(page.getByText('Homepage')).toBeVisible();
+ await page.getByRole('link', { name: 'Homepage' }).click();
+ await expect(page.getByText('The iExec Protocol Explorer')).toBeVisible();
+
+ // Navigate back to test search functionality
+ await page.goto('http://localhost:5173/arbitrum-mainnet/deals');
+
+ // And the search bar works with `0xc86054f7c22487835d9587e13b08ebb372e73ce1`
+ await page.getByRole('searchbox', { name: 'Search for addresses, deal' }).fill('0xc86054f7c22487835d9587e13b08ebb372e73ce1');
+ await page.keyboard.press('Enter');
+ await expect(page.getByText('Dataset details')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-task-behavior.spec.ts b/tests/unlogged/page-task-behavior.spec.ts
new file mode 100644
index 00000000..93f50ab2
--- /dev/null
+++ b/tests/unlogged/page-task-behavior.spec.ts
@@ -0,0 +1,27 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Task Details Page', () => {
+ test('Access task page', async ({ page }) => {
+
+ // Use a known working task ID that exists in the current data
+ const taskId = '0xcf4253493ffdab82395a3e6026e35af8fad7b57ce64c46efb5bdacafd345d52e';
+
+ // Navigate directly to the task details page
+ await page.goto(`http://localhost:5173/arbitrum-mainnet/task/${taskId}`);
+
+ // Verify that the task detail page is displayed
+ await expect(page.getByText('Task details')).toBeVisible();
+
+ // Verify that the DETAILS tab is visible
+ await expect(page.getByRole('tab', { name: 'DETAILS' })).toBeVisible();
+
+ // Verify that the DATASETS tab is visible
+ await expect(page.getByRole('tab', { name: 'DATASETS' })).toBeVisible();
+
+ // Verify that the RAW DATA tab is visible
+ await expect(page.getByRole('tab', { name: 'RAW DATA' })).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-tasks-table-behavior.spec.ts b/tests/unlogged/page-tasks-table-behavior.spec.ts
new file mode 100644
index 00000000..00ca5b67
--- /dev/null
+++ b/tests/unlogged/page-tasks-table-behavior.spec.ts
@@ -0,0 +1,25 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Page Tasks', () => {
+ test('Tasks table behavior', async ({ page }) => {
+ // Given the user navigates to the Tasks page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/tasks');
+
+ // Then the table is displayed with all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Task' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Deadline' })).toBeVisible();
+
+ // And pagination is available
+ await expect(page.getByText('Previous')).toBeVisible();
+ await expect(page.getByText('Next')).toBeVisible();
+
+ // And the Home button is present and functional
+ await expect(page.getByText('Homepage')).toBeVisible();
+ await page.getByRole('link', { name: 'Homepage' }).click();
+ await expect(page.getByText('The iExec Protocol Explorer')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/page-workerpools-table-behavior.spec.ts b/tests/unlogged/page-workerpools-table-behavior.spec.ts
new file mode 100644
index 00000000..928d667d
--- /dev/null
+++ b/tests/unlogged/page-workerpools-table-behavior.spec.ts
@@ -0,0 +1,27 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Page Workerpools', () => {
+ test('Workerpools table behavior', async ({ page }) => {
+ // Given the user navigates to the Workerpools page
+ await page.goto('http://localhost:5173/arbitrum-mainnet/workerpools');
+
+ // Then the table is displayed with all expected columns
+ await expect(page.getByRole('columnheader', { name: 'Workerpool' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Description' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Owner' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'Time' })).toBeVisible();
+ await expect(page.getByRole('columnheader', { name: 'TxHash' })).toBeVisible();
+
+ // And pagination is available
+ await expect(page.getByText('Previous')).toBeVisible();
+ await expect(page.getByText('Next')).toBeVisible();
+
+ // And the Home button is present and functional
+ await expect(page.getByText('Homepage')).toBeVisible();
+ await page.getByRole('link', { name: 'Homepage' }).click();
+ await expect(page.getByText('The iExec Protocol Explorer')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/search-bar-presence.spec.ts b/tests/unlogged/search-bar-presence.spec.ts
new file mode 100644
index 00000000..d3787785
--- /dev/null
+++ b/tests/unlogged/search-bar-presence.spec.ts
@@ -0,0 +1,14 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index - Search', () => {
+ test('Search bar presence', async ({ page }) => {
+ // Given the user navigates to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Then the search bar is visible
+ await expect(page.getByRole('searchbox', { name: 'Search for addresses, deal IDs, task IDs, or transaction hashes' })).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/search-using-enter-key.spec.ts b/tests/unlogged/search-using-enter-key.spec.ts
new file mode 100644
index 00000000..17aabf0e
--- /dev/null
+++ b/tests/unlogged/search-using-enter-key.spec.ts
@@ -0,0 +1,20 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index - Search', () => {
+ test('Search using Enter key', async ({ page }) => {
+ // Navigate to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Given the user enters `0x908ab1ca1fb0179253534d8b5f7777b8499b34f2` in the search bar
+ await page.getByRole('searchbox', { name: 'Search for addresses, deal' }).fill('0x908ab1ca1fb0179253534d8b5f7777b8499b34f2');
+
+ // When the user presses Enter
+ await page.keyboard.press('Enter');
+
+ // Then the search result page is displayed
+ await expect(page.getByText('Address details')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/valid-search.spec.ts b/tests/unlogged/valid-search.spec.ts
new file mode 100644
index 00000000..416853df
--- /dev/null
+++ b/tests/unlogged/valid-search.spec.ts
@@ -0,0 +1,20 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index - Search', () => {
+ test('Valid search', async ({ page }) => {
+ // Navigate to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Given the user enters `0xa201d2c9f3464c55639589d25fa6a3ec49c9f238`
+ await page.getByRole('searchbox', { name: 'Search for addresses, deal' }).fill('0xa201d2c9f3464c55639589d25fa6a3ec49c9f238');
+
+ // When the search is executed
+ await page.keyboard.press('Enter');
+
+ // Then the corresponding entity page is displayed
+ await expect(page.getByText('App details')).toBeVisible();
+ });
+});
\ No newline at end of file
diff --git a/tests/unlogged/view-all-links.spec.ts b/tests/unlogged/view-all-links.spec.ts
new file mode 100644
index 00000000..432821be
--- /dev/null
+++ b/tests/unlogged/view-all-links.spec.ts
@@ -0,0 +1,20 @@
+// spec: specs/plan.md
+// seed: tests/seed.spec.ts
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Index - Search', () => {
+ test('View all links', async ({ page }) => {
+ // Navigate to the homepage
+ await page.goto('http://localhost:5173/arbitrum-mainnet');
+
+ // Then the links "View all deals / tasks / apps / datasets / workerpools" are accessible on desktop
+ await expect(page.getByRole('link', { name: 'View all deals' })).toBeVisible();
+ await expect(page.getByRole('link', { name: 'View all tasks' })).toBeVisible();
+ await expect(page.getByRole('link', { name: 'View all apps' })).toBeVisible();
+ await expect(page.getByRole('link', { name: 'View all datasets' })).toBeVisible();
+ await expect(page.getByRole('link', { name: 'View all workerpools' })).toBeVisible();
+
+ // And the "View all" links are accessible on mobile (same links are responsive)
+ });
+});
\ No newline at end of file