Skip to content

Feat/testing infrastructure#104

Open
itskritix wants to merge 16 commits intomainfrom
feat/testing-infrastructure
Open

Feat/testing infrastructure#104
itskritix wants to merge 16 commits intomainfrom
feat/testing-infrastructure

Conversation

@itskritix
Copy link
Owner

No description provided.

- Add Playwright for E2E testing with page object pattern
- Add Vitest configuration for server unit tests
- Create 63 unit tests for core business logic:
  - tokens, accounts, otps, rate-limits, collaborations, permissions
- Add test fixtures and utilities for backend testing
- Create GitHub Actions CI/CD workflow for automated testing
- Update .gitignore with Playwright directories
Add test IDs to login, register, and workspace components:
- email-input, password-input, login-button, register-link
- name-input, register-button
- workspace-name-input, workspace-create-button
- workspace-container, workspace-create
Copilot AI review requested due to automatic review settings December 28, 2025 13:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive testing infrastructure to the project, including E2E testing with Playwright, unit testing with Vitest, and automated CI/CD workflows via GitHub Actions.

Key changes:

  • Added Playwright for end-to-end browser testing with fixtures and page objects
  • Implemented Vitest unit tests for core business logic (permissions, tokens, rate limits, etc.)
  • Created CI/CD pipeline with lint, unit test, integration test, and E2E test jobs
  • Added data-testid attributes to UI components for reliable test selectors

Reviewed changes

Copilot reviewed 35 out of 37 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
playwright.config.ts Playwright configuration with multi-browser support and webServer setup
e2e/tests/**/*.spec.ts E2E test suites for auth, workspaces, and pages
e2e/pages/*.page.ts Page object models for login, workspace, and editor
e2e/fixtures/*.fixture.ts Test fixtures for authentication and API client
apps/server/src/lib/__tests__/*.test.ts Unit tests for tokens, accounts, rate limits, OTPs, collaborations
packages/core/src/lib/__tests__/permissions.test.ts Unit tests for permission system
apps/server/vitest.config.ts Vitest configuration for server tests
packages/ui/src/components/**/*.tsx Added data-testid attributes for testing
.github/workflows/test.yml CI/CD workflow with lint, unit, integration, and E2E jobs
docs/TESTING.md Comprehensive testing documentation
package.json Added Playwright and test scripts

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const editorPage = new EditorPage(page);
await expect(editorPage.editor).toBeVisible({ timeout: 10000 });

await editorPage.typeInEditor('Hello, World!');
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method typeInEditor() is called but the EditorPage class only defines typeContent(). This will cause the test to fail at runtime.

Suggested change
await editorPage.typeInEditor('Hello, World!');
await editorPage.typeContent('Hello, World!');

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +34
async register(name: string, email: string, password: string) {
await this.registerLink.click();
await this.page.getByTestId('name-input').fill(name);
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The register method is missing a parameter for the confirm password field. Line 56 references confirmPassword input that should be included in the method signature to maintain consistency with the form fields.

Suggested change
async register(name: string, email: string, password: string) {
await this.registerLink.click();
await this.page.getByTestId('name-input').fill(name);
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
async register(name: string, email: string, password: string, confirmPassword: string) {
await this.registerLink.click();
await this.page.getByTestId('name-input').fill(name);
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.page.getByTestId('confirm-password-input').fill(confirmPassword);

Copilot uses AI. Check for mistakes.
}) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.goToRegister();
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method goToRegister() is called on line 10 but doesn't exist in the LoginPage class. The class only has a register() method that includes registration logic. Either add a goToRegister() method that only clicks the register link, or update the test to use the existing method.

Copilot uses AI. Check for mistakes.
Comment on lines 229 to 241
run: npm run preview &
working-directory: apps/web
env:
VITE_API_URL: http://localhost:3000

- name: Wait for services
run: |
npx wait-on http://localhost:3000/config http://localhost:4173 --timeout 60000

- name: Run E2E tests
run: npm run e2e -- --project=chromium
env:
E2E_BASE_URL: http://localhost:4173
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wait-on command is checking for port 4173, but this doesn't appear to be defined anywhere in the workflow. Vite's preview server typically uses port 4173, but there's no explicit configuration shown. The playwright.config.ts uses port 4000 as the baseURL, creating an inconsistency between the CI configuration and the local test configuration.

Suggested change
run: npm run preview &
working-directory: apps/web
env:
VITE_API_URL: http://localhost:3000
- name: Wait for services
run: |
npx wait-on http://localhost:3000/config http://localhost:4173 --timeout 60000
- name: Run E2E tests
run: npm run e2e -- --project=chromium
env:
E2E_BASE_URL: http://localhost:4173
run: npm run preview -- --port 4000 &
working-directory: apps/web
env:
VITE_API_URL: http://localhost:3000
- name: Wait for services
run: |
npx wait-on http://localhost:3000/config http://localhost:4000 --timeout 60000
- name: Run E2E tests
run: npm run e2e -- --project=chromium
env:
E2E_BASE_URL: http://localhost:4000

Copilot uses AI. Check for mistakes.
"postinstall": "node --no-warnings --loader ts-node/esm scripts/src/postinstall/index.ts"
},
"devDependencies": {
"@playwright/test": "^1.49.0",
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package.json specifies @playwright/test version ^1.49.0, but package-lock.json shows version 1.57.0 is installed. While this is within the semver range, it's worth noting that there's a significant version jump. The caret (^) allows minor version updates, which is working as intended here.

Suggested change
"@playwright/test": "^1.49.0",
"@playwright/test": "^1.57.0",

Copilot uses AI. Check for mistakes.
await createPageButton.click();

const editorPage = new EditorPage(page);
await expect(editorPage.editor).toBeVisible({ timeout: 10000 });
Copy link

Copilot AI Dec 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EditorPage class has properties titleInput and contentEditor, but the test code references editorPage.editor and editorPage.typeInEditor() which don't exist in the class. The class defines contentEditor and typeContent() instead. This mismatch will cause the E2E tests to fail.

Copilot uses AI. Check for mistakes.
@greptile-apps
Copy link

greptile-apps bot commented Dec 28, 2025

Greptile Summary

This PR establishes comprehensive testing infrastructure for the project, adding Vitest for unit/integration tests and Playwright for E2E tests. The changes include 63 unit tests covering authentication, permissions, rate limiting, and collaboration logic, plus 14 E2E tests for auth flows, workspace management, and page editing.

Key additions:

  • CI/CD pipeline with parallel lint, unit, integration, and E2E test jobs
  • Test utilities and fixtures for both backend (Fastify) and frontend (Playwright) testing
  • data-testid attributes added to UI components for E2E test selectors
  • Comprehensive testing documentation (docs/TESTING.md) with coverage status and roadmap
  • Import path fix in field-value.ts to avoid circular dependencies

Infrastructure highlights:

  • GitHub Actions workflow properly configures PostgreSQL, Redis, and MinIO services
  • Vitest uses singleFork pool option to prevent test isolation issues
  • Playwright configured with CI detection, retries, and proper webServer setup
  • Test fixtures use randomUUID() for unique test IDs

Issues addressed from previous review:

  • Both DATABASE_URL and POSTGRES_URL now defined in workflow
  • Global setup improved with error logging on final retry

The testing foundation is solid and follows best practices. Some E2E tests have conditional logic that could silently pass, and a few page objects lack proper wait conditions, but these are minor issues that don't block the PR.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk
  • The testing infrastructure is well-designed with proper CI/CD setup, comprehensive test coverage for core functionality, and good separation of concerns. Previous review comments have been addressed. Minor issues exist with conditional test logic that could silently pass and missing wait conditions in some page objects, but these don't introduce bugs into production code—they only affect test reliability, which can be improved iteratively.
  • Pay attention to e2e/tests/pages/page.spec.ts, e2e/pages/workspace.page.ts, and e2e/pages/login.page.ts for potential test flakiness due to conditional logic and missing wait conditions.

Important Files Changed

Filename Overview
.github/workflows/test.yml Comprehensive CI pipeline with lint, unit, integration, and E2E tests. Proper service dependencies and parallelization. Both DATABASE_URL and POSTGRES_URL now defined to avoid previous issue.
playwright.config.ts Well-configured Playwright setup with proper CI detection, retries, parallel execution control, and webServer configuration for local development.
apps/server/src/test/app.ts Test app factory with proper Fastify setup. injectAuth helper creates and logs in test users, though registration success isn't validated before login attempt.
e2e/global-setup.ts Server readiness check with 30 retries. Improved error handling that now logs on final retry, addressing empty catch concern from previous review.
e2e/fixtures/auth.fixture.ts Playwright fixtures for authenticated users. Uses randomUUID for test IDs (better than Math.random). Some duplication in registration logic between fixtures.
e2e/pages/login.page.ts Page object for login flow. The register method navigates to registration but reuses emailInput/passwordInput locators which may have different test IDs on registration page.
e2e/pages/workspace.page.ts Workspace page object. createSpace method lacks wait condition after creation (unlike createWorkspace), potentially causing race conditions.
e2e/tests/pages/page.spec.ts Page editor E2E tests with conditional workspace creation and button visibility checks. Tests may silently pass without actually testing if elements aren't visible.

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub Actions
    participant Services as CI Services
    participant Tests as Test Suites
    
    Dev->>GH: Push/PR to main
    GH->>GH: Start CI Pipeline
    
    par Lint & Type Check
        GH->>Tests: npm run lint
        GH->>Tests: npm run compile
    end
    
    par Unit Tests
        GH->>Services: Start Redis
        Services-->>GH: Redis Ready
        GH->>Tests: Run Vitest (server unit tests)
        Tests-->>GH: Unit Test Results
    end
    
    par Integration Tests
        GH->>Services: Start PostgreSQL + Redis + MinIO
        Services-->>GH: Services Ready
        GH->>Services: Create MinIO bucket
        GH->>Services: Run DB migrations
        GH->>Tests: Run Vitest (integration tests)
        Tests-->>GH: Integration Test Results
    end
    
    par E2E Tests
        GH->>Services: Start PostgreSQL + Redis + MinIO
        Services-->>GH: Services Ready
        GH->>GH: Install Playwright browsers
        GH->>Services: Create MinIO bucket
        GH->>Services: Run DB migrations
        GH->>GH: Build packages
        GH->>Services: Start server + web app
        GH->>Services: Wait for services (wait-on)
        GH->>Tests: Run Playwright E2E tests
        Tests-->>GH: E2E Test Results
        Tests-->>GH: Upload artifacts (reports/screenshots)
    end
    
    GH-->>Dev: Pipeline Results
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. packages/ui/src/components/accounts/email-register.tsx, line 172-179 (link)

    style: confirmPassword field is missing data-testid attribute while other form fields have test selectors

    Should the confirm password field also have a test selector for completeness?

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

36 files reviewed, 20 comments

Edit Code Review Agent Settings | Greptile

const { token } = generateToken(DEVICE_ID);
const parsed = parseToken(token);

const expectedSecret = token.slice(4 + 28);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Magic numbers (4 + 28) hardcode token structure assumptions. Consider extracting these from the token implementation or using constants.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/lib/__tests__/tokens.test.ts
Line: 58:58

Comment:
**style:** Magic numbers (4 + 28) hardcode token structure assumptions. Consider extracting these from the token implementation or using constants.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +161 to +163
expect(result.canAccess).toBe(false);
expect(result.accessSource).toBe('none');
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: missing assertion for effectiveRole being null when access is denied

Suggested change
expect(result.canAccess).toBe(false);
expect(result.accessSource).toBe('none');
});
expect(result.canAccess).toBe(false);
expect(result.accessSource).toBe('none');
expect(result.effectiveRole).toBeNull();

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/lib/__tests__/permissions.test.ts
Line: 161:163

Comment:
**style:** missing assertion for effectiveRole being null when access is denied

```suggestion
      expect(result.canAccess).toBe(false);
      expect(result.accessSource).toBe('none');
      expect(result.effectiveRole).toBeNull();
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

}

const uniqueCodes = new Set(codes);
expect(uniqueCodes.size).toBeGreaterThan(950);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The 95% uniqueness threshold might be too lenient for security-critical OTP generation. With 1M possible 6-digit codes, expect near 100% uniqueness from 1000 samples. Is this threshold intentionally conservative to account for potential implementation issues, or should it be stricter?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/lib/__tests__/otps.test.ts
Line: 34:34

Comment:
**style:** The 95% uniqueness threshold might be too lenient for security-critical OTP generation. With 1M possible 6-digit codes, expect near 100% uniqueness from 1000 samples. Is this threshold intentionally conservative to account for potential implementation issues, or should it be stricter?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +25 to +27
await expect(page.getByText('String must contain at least')).toBeVisible({
timeout: 5000,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using partial text match for validation errors is fragile and may break if error messages change. Consider using a more specific test ID or data attribute.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/tests/auth/register.spec.ts
Line: 25:27

Comment:
**style:** Using partial text match for validation errors is fragile and may break if error messages change. Consider using a more specific test ID or data attribute.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

await page.getByTestId('name-input').fill(testUser.name);
await page.getByTestId('email-input').fill(testUser.email);
await page.getByTestId('password-input').fill(testUser.password);
await page.locator('input[name="confirmPassword"]').fill(testUser.password);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Inconsistent selector usage - mixing getByTestId with locator('input[name="confirmPassword"]'). Consider adding a test ID to the confirm password field for consistency.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/tests/auth/register.spec.ts
Line: 56:56

Comment:
**style:** Inconsistent selector usage - mixing `getByTestId` with `locator('input[name="confirmPassword"]')`. Consider adding a test ID to the confirm password field for consistency.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +28 to +36
const createPageButton = page.getByTestId('create-page-button');
if (
await createPageButton.isVisible({ timeout: 5000 }).catch(() => false)
) {
await createPageButton.click();

const editorPage = new EditorPage(page);
await expect(editorPage.editor).toBeVisible({ timeout: 10000 });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Test silently passes if create-page-button is not visible, potentially hiding UI issues

Suggested change
const createPageButton = page.getByTestId('create-page-button');
if (
await createPageButton.isVisible({ timeout: 5000 }).catch(() => false)
) {
await createPageButton.click();
const editorPage = new EditorPage(page);
await expect(editorPage.editor).toBeVisible({ timeout: 10000 });
}
const createPageButton = page.getByTestId('create-page-button');
await expect(createPageButton).toBeVisible({ timeout: 5000 });
await createPageButton.click();
const editorPage = new EditorPage(page);
await expect(editorPage.editor).toBeVisible({ timeout: 10000 });
Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/tests/pages/page.spec.ts
Line: 28:36

Comment:
**logic:** Test silently passes if create-page-button is not visible, potentially hiding UI issues

```suggestion
    const createPageButton = page.getByTestId('create-page-button');
    await expect(createPageButton).toBeVisible({ timeout: 5000 });
    await createPageButton.click();

    const editorPage = new EditorPage(page);
    await expect(editorPage.editor).toBeVisible({ timeout: 10000 });
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +39 to +44
test('should type in the editor', async ({ page }) => {
const createPageButton = page.getByTestId('create-page-button');
if (
await createPageButton.isVisible({ timeout: 5000 }).catch(() => false)
) {
await createPageButton.click();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Code duplication - the page creation logic is repeated from the previous test

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: e2e/tests/pages/page.spec.ts
Line: 39:44

Comment:
**style:** Code duplication - the page creation logic is repeated from the previous test

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 45 to 53
await app.inject({
method: 'POST',
url: '/client/v1/accounts/emails/register',
payload: {
email,
password,
name: 'Test User',
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Registration request doesn't check for success response - if registration fails, login will also fail but error won't be clear. Should the registration response be validated before proceeding to login?

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/test/app.ts
Line: 45:53

Comment:
**logic:** Registration request doesn't check for success response - if registration fails, login will also fail but error won't be clear. Should the registration response be validated before proceeding to login?

How can I resolve this? If you propose a fix, please make it concise.

payload: { email, password },
});

const data = JSON.parse(loginResponse.body);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: JSON.parse can throw if response body is invalid JSON - consider wrapping in try/catch

Suggested change
const data = JSON.parse(loginResponse.body);
try {
const data = JSON.parse(loginResponse.body);
return data.type === 'success' ? data.token : null;
} catch {
return null;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/server/src/test/app.ts
Line: 61:61

Comment:
**logic:** `JSON.parse` can throw if response body is invalid JSON - consider wrapping in try/catch

```suggestion
  try {
    const data = JSON.parse(loginResponse.body);
    return data.type === 'success' ? data.token : null;
  } catch {
    return null;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +15 to +16
POSTGRES_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Environment variable inconsistency: POSTGRES_URL is defined but migration steps use DATABASE_URL

Suggested change
POSTGRES_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
DATABASE_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
REDIS_URL: redis://localhost:6379/0
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/test.yml
Line: 15:16

Comment:
**logic:** Environment variable inconsistency: `POSTGRES_URL` is defined but migration steps use `DATABASE_URL`

```suggestion
  DATABASE_URL: postgresql://brainbox:brainbox@localhost:5432/brainbox
  REDIS_URL: redis://localhost:6379/0
```

How can I resolve this? If you propose a fix, please make it concise.

- Add editor alias and typeInEditor method to EditorPage
- Add goToRegister method to LoginPage
- Fix CI port mismatch (use port 4000 consistently)
- Add DATABASE_URL env var alongside POSTGRES_URL
- Use crypto.randomUUID for better test ID uniqueness
- Extract registerTestUser helper to avoid duplication
- Add registration response check and JSON.parse try/catch
- Add wait after space creation in WorkspacePage
- Log errors in global-setup on final retry
- Remove redundant length assertion in collaborations test
- Fix logout test to skip gracefully if UI elements missing
@itskritix
Copy link
Owner Author

@greptile

- Use npx vite preview directly instead of npm run serve
- Remove verbose debug logging
- Clean up wait-for-services step
…nsion

The postgres:16-alpine image doesn't include pgvector extension,
causing migration 00017_create_vector_extension to fail.
The preview server needs the same Cross-Origin headers as the dev server
for SharedArrayBuffer to work, which is required by SQLite WASM.

- Add preview config to vite.config.js with port 4000 and headers
- Simplify workflow command since port is in config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants