Skip to content

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Nov 17, 2025

Description


Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Organize and standardize the Cypress E2E test suite by centralizing selectors, configuration, and utility functions, and refactor existing specs to leverage these new modules for improved consistency and maintainability.

Enhancements:

  • Centralize all data-testid lookups into structured selector objects (e.g., AuthSelectors, ShareSelectors, AddPageSelectors, WorkspaceSelectors) and introduce helper functions (byTestId, byTestIdPrefix, byTestIdContains) in support/selectors.ts
  • Introduce TestConfig for unified environment variable access and logging, along with generateRandomEmail and logAppFlowyEnvironment utilities for consistent test data and debugging
  • Add common support modules in cypress/support for test-helpers, API mocks (api-mocks.ts), chat-specific mocks (chat-mocks.ts), and exception handlers to reduce duplication and streamline test setup
  • Refactor dozens of spec files to replace raw cy.get calls, inline configuration, and duplicated logic with the new selectors and utility functions

@sourcery-ai
Copy link

sourcery-ai bot commented Nov 17, 2025

Reviewer's Guide

Reorganizes the Cypress E2E test suite by introducing centralized support utilities and configuration, expanding selector abstractions in selectors.ts, and refactoring all spec files to use the new helper APIs for consistent, maintainable test code.

Class diagram for new and updated selector helpers in selectors.ts

classDiagram
    byTestId : string
    byTestIdPrefix : string
    byTestIdContains : string

    class AuthSelectors {
      emailInput()
      magicLinkButton()
      enterCodeManuallyButton()
      otpCodeInput()
      otpSubmitButton()
      passwordSignInButton()
      passwordInput()
      passwordSubmitButton()
    }

    class WorkspaceSelectors {
      dropdownTrigger()
      dropdownContent()
    }

    class AddPageSelectors {
      inlineAddButton()
      addAIChatButton()
    }

    class ModelSelectorSelectors {
      button()
      options()
    }

    class SpaceSelectors {
      moreActionsButton()
    }

    byTestId <|-- AuthSelectors
    byTestId <|-- WorkspaceSelectors
    byTestId <|-- AddPageSelectors
    byTestId <|-- ModelSelectorSelectors
    byTestId <|-- SpaceSelectors
    byTestIdPrefix <|-- ModelSelectorSelectors
    byTestIdContains <|-- ModelSelectorSelectors
Loading

Class diagram for new centralized Cypress support modules

classDiagram
    class chat-mocks {
      +setupChatApiStubs(options)
      +mockChatMessage(content, messageId, authorType)
      +mockEmptyChatMessages()
      +mockChatSettings(aiModel, ragIds)
      +mockUpdateChatSettings()
      +mockModelList(modelNames, defaultModel)
      +mockRelatedQuestions(messageId, questions)
      +mockSendMessage(responseContent)
      +mockChatStreaming(chunks)
      +mockChatError(errorMessage)
      DEFAULT_MESSAGE_ID : number
      DEFAULT_MESSAGE_CONTENT : string
    }

    class api-mocks {
      +createAuthResponse(email, accessToken, refreshToken, userId)
      +mockAuthEndpoints(email, accessToken, refreshToken, userId)
      +mockOTPEndpoints(email, accessToken, refreshToken, userId)
      +mockWorkspaceEndpoints(workspaceId, userId, workspaceName)
      +mockUserVerification(accessToken, isNewUser)
      +mockCompleteAuthFlow(email, accessToken, refreshToken, userId, workspaceId)
    }

    class exception-handlers {
      +setupCommonExceptionHandlers(additionalPatterns)
      +ignoreAllExceptions()
      IGNORED_ERROR_PATTERNS : string[]
    }

    class test-config {
      TestConfig : object
      generateRandomEmail()
      logAppFlowyEnvironment()
    }

    class test-helpers {
      // (methods not shown, placeholder for future helpers)
    }
Loading

File-Level Changes

Change Details Files
Added centralized support utilities and configuration
  • Created test-config.ts for environment variables and logging
  • Added test-helpers.ts with common test utilities
  • Introduced api-mocks.ts for shared API intercept patterns
  • Added exception-handlers.ts to handle and ignore known errors
  • Added chat-mocks.ts for chat-specific API mocking
cypress/support/test-config.ts
cypress/support/test-helpers.ts
cypress/support/api-mocks.ts
cypress/support/exception-handlers.ts
cypress/support/chat-mocks.ts
Enhanced selector abstractions in support/selectors.ts
  • Added byTestIdPrefix and byTestIdContains helpers
  • Expanded AuthSelectors with new methods (magicLinkButton, otpCodeInput, etc.)
  • Imported selector helpers in various spec files
cypress/support/selectors.ts
Centralized test configuration usage and email generation
  • Replaced inline Cypress.env calls with TestConfig in auth flows
  • Replaced uuidv4-based email strings with generateRandomEmail()
  • Imported and used TestConfig and generateRandomEmail across OTP, OAuth, and password login specs
cypress/e2e/auth/otp-login.cy.ts
cypress/e2e/auth/oauth-login.cy.ts
cypress/e2e/auth/password-login.cy.ts
cypress/e2e/auth/login-logout.cy.ts
Refactored spec files to use new selector abstractions
  • Replaced raw cy.get('[data-testid=…]') calls with AuthSelectors, ShareSelectors, AddPageSelectors, ModelSelectorSelectors, WorkspaceSelectors, and byTestId helpers
  • Updated imports to include selector modules and TestConfig where needed
  • Consolidated selector patterns for byTestId, byTestIdPrefix, and byTestIdContains
cypress/e2e/auth/*.cy.ts
cypress/e2e/page/*.cy.ts
cypress/e2e/chat/*.cy.ts
cypress/e2e/account/**/*.cy.ts
cypress/e2e/space/*.cy.ts
cypress/e2e/database/*.cy.ts
cypress/e2e/editor/*.cy.ts
cypress/e2e/app/**/*.cy.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider adopting a single, consistent pattern for querying data-testid elements (either the centralized selectors in selectors.ts or the byTestId helper) across all specs to avoid mixing raw strings and improve maintainability.
  • Double-check that no direct cy.get('[data-testid="…"]') calls remain by grepping for raw test IDs—every selector should ideally come from your support selectors or byTestId functions.
  • To reduce repetitive imports, you could register common utilities (TestConfig, exception handlers, test-helpers) in cypress/support/index.ts so individual specs don’t need to import them explicitly.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider adopting a single, consistent pattern for querying `data-testid` elements (either the centralized selectors in selectors.ts or the byTestId helper) across all specs to avoid mixing raw strings and improve maintainability.
- Double-check that no direct cy.get('[data-testid="…"]') calls remain by grepping for raw test IDs—every selector should ideally come from your support selectors or byTestId functions.
- To reduce repetitive imports, you could register common utilities (TestConfig, exception handlers, test-helpers) in cypress/support/index.ts so individual specs don’t need to import them explicitly.

## Individual Comments

### Comment 1
<location> `cypress/support/chat-mocks.ts:35` </location>
<code_context>
+ * @param messageId - Message ID (default: 101)
+ * @param authorType - Author type (3 = assistant, 1 = user)
+ */
+export const mockChatMessage = (
+  content = DEFAULT_MESSAGE_CONTENT,
+  messageId = DEFAULT_MESSAGE_ID,
</code_context>

<issue_to_address>
**issue (complexity):** Consider introducing a factory helper to centralize and reduce repetitive cy.intercept logic in your mock endpoint definitions.

Consider extracting a small factory to remove the 90% duplication in your `cy.intercept` calls. For example, you can add a helper like this at the top of `chat-mocks.ts`:

```ts
type MockOpts<Args> = {
  method: 'GET' | 'POST' | 'PATCH'
  url: string
  alias: string
  toBody: (args: Args) => unknown
}

function createMockEndpoint<Args>(opts: MockOpts<Args>) {
  return (args: Args) => {
    cy.intercept(opts.method, opts.url, {
      statusCode: 200,
      body: opts.toBody(args),
    }).as(opts.alias)
  }
}

// common success wrapper
const success = <T>(data: T) => ({ code: 0, message: 'success', data })
```

Then you can rewrite your existing mocks in just one line each:

```ts
export const mockChatSettings =
  createMockEndpoint<{ aiModel: string; ragIds: string[] }>({
    method: 'GET',
    url: '**/api/chat/**/settings**',
    alias: 'getChatSettings',
    toBody: ({ aiModel, ragIds }) =>
      success({ rag_ids: ragIds, metadata: { ai_model: aiModel } }),
  })

export const mockUpdateChatSettings =
  createMockEndpoint<{}>({
    method: 'PATCH',
    url: '**/api/chat/**/settings**',
    alias: 'updateChatSettings',
    toBody: () => ({ code: 0, message: 'success' }), // no data
  })
```

You can apply the same pattern to all your other mocks, or split them into separate modules (messages, settings, models) to keep each file small and focused.
</issue_to_address>

### Comment 2
<location> `cypress/support/api-mocks.ts:48` </location>
<code_context>
+ * Mocks standard authentication endpoints (password login, verify, refresh)
+ * Returns the generated IDs and tokens for use in tests
+ */
+export const mockAuthEndpoints = (
+  email: string,
+  accessToken = `mock-token-${uuidv4()}`,
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring repeated cy.intercept boilerplate into small helper functions to simplify and shorten each mock function.

```markdown
You still have quite a bit of repeated “cy.intercept(…, {…}).as(…)” boilerplate for verify-user, refresh-token and workspace payloads. Pulling those into tiny helpers will make each mock-fn just a few lines long:

```ts
// support/api-mocks.ts

// 1) small wrapper to reduce intercept boilerplate
function setupIntercept(
  method: Cypress.RouteMatcher['method'],
  url: string,
  alias: string,
  body: unknown,
) {
  cy.intercept(method, url, { statusCode: 200, body }).as(alias)
}

// 2) verify-user is identical across many mocks
function interceptVerifyUser(apiUrl: string, accessToken: string, isNew = false) {
  setupIntercept(
    'GET',
    `${apiUrl}/api/user/verify/${accessToken}`,
    'verifyUser',
    { code: 0, data: { is_new: isNew }, message: 'User verified successfully' },
  )
}

// 3) refresh-token is identical across auth & otp
function interceptRefresh(
  gotrueUrl: string,
  email: string,
  accessToken: string,
  refreshToken: string,
  userId: string,
) {
  setupIntercept(
    'POST',
    `${gotrueUrl}/token?grant_type=refresh_token`,
    'refreshToken',
    createAuthResponse(email, accessToken, refreshToken, userId),
  )
}

// 4) workspace payload repeats itself: factor it out
function makeWorkspacePayload(workspaceId: string, workspaceName: string, userId: string) {
  const ws = {
    workspace_id: workspaceId,
    workspace_name: workspaceName,
    icon: '',
    created_at: Date.now().toString(),
    database_storage_id: '',
    owner_uid: 1,
    owner_name: 'Test User',
    member_count: 1,
  }
  return {
    code: 0,
    data: {
      user_profile: { uuid: userId },
      visiting_workspace: ws,
      workspaces: [ws],
    },
  }
}

// Now your mocks shrink to:

export const mockAuthEndpoints = (
  email: string,
  accessToken = `mock-token-${uuidv4()}`,
  refreshToken = `mock-refresh-${uuidv4()}`,
  userId = uuidv4(),
) => {
  const { gotrueUrl, apiUrl } = TestConfig

  setupIntercept(
    'POST',
    `${gotrueUrl}/token?grant_type=password`,
    'passwordLogin',
    createAuthResponse(email, accessToken, refreshToken, userId),
  )
  interceptVerifyUser(apiUrl, accessToken)
  interceptRefresh(gotrueUrl, email, accessToken, refreshToken, userId)

  return { userId, accessToken, refreshToken }
}

export const mockOTPEndpoints = (
  email: string,
  accessToken = `mock-otp-token-${uuidv4()}`,
  refreshToken = `mock-otp-refresh-${uuidv4()}`,
  userId = uuidv4(),
) => {
  const { gotrueUrl, apiUrl } = TestConfig

  setupIntercept('POST', `${gotrueUrl}/otp`, 'sendOTP', {})
  setupIntercept(
    'POST',
    `${gotrueUrl}/verify`,
    'verifyOTP',
    createAuthResponse(email, accessToken, refreshToken, userId),
  )
  interceptVerifyUser(apiUrl, accessToken)
  interceptRefresh(gotrueUrl, email, accessToken, refreshToken, userId)

  return { userId, accessToken, refreshToken }
}

export const mockWorkspaceEndpoints = (
  workspaceId = uuidv4(),
  userId = uuidv4(),
  workspaceName = 'Test Workspace',
) => {
  const { apiUrl } = TestConfig

  setupIntercept(
    'GET',
    `${apiUrl}/api/user/workspace`,
    'getUserWorkspaceInfo',
    makeWorkspacePayload(workspaceId, workspaceName, userId),
  )

  return { workspaceId, userId }
}
```

This removes most of the copy/paste and keeps each mock-fn under ~10 lines.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

* @param messageId - Message ID (default: 101)
* @param authorType - Author type (3 = assistant, 1 = user)
*/
export const mockChatMessage = (
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider introducing a factory helper to centralize and reduce repetitive cy.intercept logic in your mock endpoint definitions.

Consider extracting a small factory to remove the 90% duplication in your cy.intercept calls. For example, you can add a helper like this at the top of chat-mocks.ts:

type MockOpts<Args> = {
  method: 'GET' | 'POST' | 'PATCH'
  url: string
  alias: string
  toBody: (args: Args) => unknown
}

function createMockEndpoint<Args>(opts: MockOpts<Args>) {
  return (args: Args) => {
    cy.intercept(opts.method, opts.url, {
      statusCode: 200,
      body: opts.toBody(args),
    }).as(opts.alias)
  }
}

// common success wrapper
const success = <T>(data: T) => ({ code: 0, message: 'success', data })

Then you can rewrite your existing mocks in just one line each:

export const mockChatSettings =
  createMockEndpoint<{ aiModel: string; ragIds: string[] }>({
    method: 'GET',
    url: '**/api/chat/**/settings**',
    alias: 'getChatSettings',
    toBody: ({ aiModel, ragIds }) =>
      success({ rag_ids: ragIds, metadata: { ai_model: aiModel } }),
  })

export const mockUpdateChatSettings =
  createMockEndpoint<{}>({
    method: 'PATCH',
    url: '**/api/chat/**/settings**',
    alias: 'updateChatSettings',
    toBody: () => ({ code: 0, message: 'success' }), // no data
  })

You can apply the same pattern to all your other mocks, or split them into separate modules (messages, settings, models) to keep each file small and focused.

@appflowy appflowy merged commit e3cdc74 into main Nov 17, 2025
10 checks passed
@appflowy appflowy deleted the organize_e2e_test2 branch November 17, 2025 07:45
josue693 pushed a commit to josue693/AppFlowy-Web that referenced this pull request Dec 21, 2025
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