Skip to content

Integration Testing with Element Plus

Overview

Integration testing ensures that different parts of your application work together correctly. When using Element Plus, this involves testing component interactions, form workflows, navigation patterns, and API integrations. This guide covers comprehensive integration testing strategies.

Testing Setup

E2E Testing with Playwright

bash
# Install Playwright
npm install --save-dev @playwright/test
npx playwright install
javascript
// playwright.config.js
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
})

Testing with Cypress

bash
# Install Cypress
npm install --save-dev cypress
javascript
// cypress.config.js
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    defaultCommandTimeout: 10000,
    requestTimeout: 10000,
    responseTimeout: 10000
  },
  component: {
    devServer: {
      framework: 'vue',
      bundler: 'vite',
    },
  },
})
javascript
// cypress/support/commands.js

// Custom commands for Element Plus components
Cypress.Commands.add('getByTestId', (testId) => {
  return cy.get(`[data-testid="${testId}"]`)
})

Cypress.Commands.add('fillElInput', (testId, value) => {
  cy.getByTestId(testId).find('input').clear().type(value)
})

Cypress.Commands.add('selectElOption', (testId, value) => {
  cy.getByTestId(testId).click()
  cy.get('.el-select-dropdown').contains(value).click()
})

Cypress.Commands.add('clickElButton', (testId) => {
  cy.getByTestId(testId).click()
})

Cypress.Commands.add('checkElCheckbox', (testId) => {
  cy.getByTestId(testId).find('input[type="checkbox"]').check({ force: true })
})

Cypress.Commands.add('uncheckElCheckbox', (testId) => {
  cy.getByTestId(testId).find('input[type="checkbox"]').uncheck({ force: true })
})

Cypress.Commands.add('selectElDate', (testId, date) => {
  cy.getByTestId(testId).click()
  cy.get('.el-date-picker__header-label').first().click()
  cy.get('.el-year-table').contains(date.getFullYear().toString()).click()
  cy.get('.el-month-table').contains(date.toLocaleString('default', { month: 'short' })).click()
  cy.get('.el-date-table').contains(date.getDate().toString()).click()
})

Cypress.Commands.add('waitForElLoading', (timeout = 10000) => {
  cy.get('.el-loading-mask', { timeout }).should('not.exist')
})

Cypress.Commands.add('expectElMessage', (type, message) => {
  cy.get(`.el-message--${type}`).should('contain', message)
})

Cypress.Commands.add('expectElNotification', (type, title) => {
  cy.get(`.el-notification--${type}`).should('contain', title)
})

Form Integration Testing

Complex Form Workflow

javascript
// tests/e2e/user-registration.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('User Registration Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/register')
  })
  
  test('completes full registration process', async ({ page }) => {
    // Step 1: Personal Information
    await page.fill('[data-testid="first-name"] input', 'John')
    await page.fill('[data-testid="last-name"] input', 'Doe')
    await page.fill('[data-testid="email"] input', 'john.doe@example.com')
    await page.fill('[data-testid="phone"] input', '+1234567890')
    
    // Select date of birth
    await page.click('[data-testid="birth-date"]')
    await page.click('.el-year-table td:has-text("1990")')
    await page.click('.el-month-table td:has-text("Jan")')
    await page.click('.el-date-table td:has-text("15")')
    
    // Select gender
    await page.click('[data-testid="gender"]')
    await page.click('.el-select-dropdown__item:has-text("Male")')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 2: Account Information
    await page.fill('[data-testid="username"] input', 'johndoe123')
    await page.fill('[data-testid="password"] input', 'SecurePassword123!')
    await page.fill('[data-testid="confirm-password"] input', 'SecurePassword123!')
    
    // Security questions
    await page.click('[data-testid="security-question-1"]')
    await page.click('.el-select-dropdown__item:has-text("What is your mother\'s maiden name?")')
    await page.fill('[data-testid="security-answer-1"] input', 'Smith')
    
    await page.click('[data-testid="security-question-2"]')
    await page.click('.el-select-dropdown__item:has-text("What was your first pet\'s name?")')
    await page.fill('[data-testid="security-answer-2"] input', 'Buddy')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 3: Preferences
    await page.check('[data-testid="newsletter"] input')
    await page.check('[data-testid="notifications"] input')
    
    // Select interests
    await page.check('[data-testid="interest-technology"] input')
    await page.check('[data-testid="interest-sports"] input')
    
    // Select language
    await page.click('[data-testid="language"]')
    await page.click('.el-select-dropdown__item:has-text("English")')
    
    // Select timezone
    await page.click('[data-testid="timezone"]')
    await page.click('.el-select-dropdown__item:has-text("UTC-5")')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 4: Review and Submit
    await expect(page.locator('[data-testid="review-name"]')).toContainText('John Doe')
    await expect(page.locator('[data-testid="review-email"]')).toContainText('john.doe@example.com')
    await expect(page.locator('[data-testid="review-username"]')).toContainText('johndoe123')
    
    // Accept terms
    await page.check('[data-testid="accept-terms"] input')
    
    // Submit form
    await page.click('[data-testid="submit-registration"]')
    
    // Wait for success message
    await expect(page.locator('.el-message--success')).toContainText('Registration successful')
    
    // Should redirect to login page
    await expect(page).toHaveURL('/login')
  })
  
  test('validates form fields correctly', async ({ page }) => {
    // Try to proceed without filling required fields
    await page.click('[data-testid="next-step"]')
    
    // Should show validation errors
    await expect(page.locator('.el-form-item__error')).toContainText('Please input first name')
    
    // Fill invalid email
    await page.fill('[data-testid="email"] input', 'invalid-email')
    await page.blur('[data-testid="email"] input')
    
    await expect(page.locator('.el-form-item__error')).toContainText('Please input valid email')
    
    // Test password confirmation
    await page.fill('[data-testid="first-name"] input', 'John')
    await page.fill('[data-testid="last-name"] input', 'Doe')
    await page.fill('[data-testid="email"] input', 'john@example.com')
    await page.click('[data-testid="next-step"]')
    
    await page.fill('[data-testid="password"] input', 'password123')
    await page.fill('[data-testid="confirm-password"] input', 'different')
    await page.blur('[data-testid="confirm-password"] input')
    
    await expect(page.locator('.el-form-item__error')).toContainText('Passwords do not match')
  })
  
  test('handles server errors gracefully', async ({ page }) => {
    // Mock server error
    await page.route('**/api/register', route => {
      route.fulfill({
        status: 400,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'Email already exists' })
      })
    })
    
    // Fill form with valid data
    await page.fill('[data-testid="first-name"] input', 'John')
    await page.fill('[data-testid="last-name"] input', 'Doe')
    await page.fill('[data-testid="email"] input', 'existing@example.com')
    await page.click('[data-testid="next-step"]')
    
    await page.fill('[data-testid="username"] input', 'johndoe')
    await page.fill('[data-testid="password"] input', 'password123')
    await page.fill('[data-testid="confirm-password"] input', 'password123')
    await page.click('[data-testid="next-step"]')
    
    await page.click('[data-testid="next-step"]')
    
    await page.check('[data-testid="accept-terms"] input')
    await page.click('[data-testid="submit-registration"]')
    
    // Should show error message
    await expect(page.locator('.el-message--error')).toContainText('Email already exists')
  })
})

Cypress Form Testing

javascript
// cypress/e2e/contact-form.cy.js
describe('Contact Form Integration', () => {
  beforeEach(() => {
    cy.visit('/contact')
  })
  
  it('submits contact form successfully', () => {
    // Fill personal information
    cy.fillElInput('name', 'John Doe')
    cy.fillElInput('email', 'john@example.com')
    cy.fillElInput('phone', '+1234567890')
    
    // Select inquiry type
    cy.selectElOption('inquiry-type', 'General Question')
    
    // Select priority
    cy.getByTestId('priority').within(() => {
      cy.get('input[value="medium"]').check({ force: true })
    })
    
    // Fill message
    cy.getByTestId('message').find('textarea').type('This is a test message for the contact form.')
    
    // Upload file
    cy.getByTestId('file-upload').selectFile('cypress/fixtures/test-document.pdf')
    
    // Accept privacy policy
    cy.checkElCheckbox('privacy-policy')
    
    // Submit form
    cy.clickElButton('submit-form')
    
    // Wait for loading to complete
    cy.waitForElLoading()
    
    // Verify success message
    cy.expectElMessage('success', 'Your message has been sent successfully')
    
    // Verify form is reset
    cy.getByTestId('name').find('input').should('have.value', '')
    cy.getByTestId('email').find('input').should('have.value', '')
  })
  
  it('validates required fields', () => {
    cy.clickElButton('submit-form')
    
    // Check validation errors
    cy.get('.el-form-item__error').should('contain', 'Please input your name')
    cy.get('.el-form-item__error').should('contain', 'Please input your email')
    cy.get('.el-form-item__error').should('contain', 'Please select inquiry type')
  })
  
  it('handles file upload validation', () => {
    // Try to upload invalid file type
    cy.getByTestId('file-upload').selectFile('cypress/fixtures/invalid-file.txt')
    
    cy.expectElMessage('error', 'Only PDF, DOC, and DOCX files are allowed')
    
    // Try to upload file that's too large
    cy.getByTestId('file-upload').selectFile('cypress/fixtures/large-file.pdf')
    
    cy.expectElMessage('error', 'File size cannot exceed 5MB')
  })
})

Data Table Integration Testing

Complex Table Operations

javascript
// tests/e2e/user-management.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('User Management Table', () => {
  test.beforeEach(async ({ page }) => {
    // Mock API responses
    await page.route('**/api/users**', route => {
      const url = new URL(route.request().url())
      const page = parseInt(url.searchParams.get('page') || '1')
      const size = parseInt(url.searchParams.get('size') || '20')
      const search = url.searchParams.get('search') || ''
      
      const allUsers = Array.from({ length: 100 }, (_, i) => ({
        id: i + 1,
        name: `User ${i + 1}`,
        email: `user${i + 1}@example.com`,
        status: ['active', 'inactive', 'pending'][i % 3],
        role: ['admin', 'user', 'moderator'][i % 3],
        createdAt: new Date(2023, 0, i + 1).toISOString()
      }))
      
      let filteredUsers = allUsers
      if (search) {
        filteredUsers = allUsers.filter(user => 
          user.name.toLowerCase().includes(search.toLowerCase()) ||
          user.email.toLowerCase().includes(search.toLowerCase())
        )
      }
      
      const start = (page - 1) * size
      const end = start + size
      const paginatedUsers = filteredUsers.slice(start, end)
      
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          data: paginatedUsers,
          total: filteredUsers.length,
          page,
          size
        })
      })
    })
    
    await page.goto('/admin/users')
    await page.waitForLoadState('networkidle')
  })
  
  test('loads and displays user data', async ({ page }) => {
    // Check table headers
    await expect(page.locator('th')).toContainText(['Name', 'Email', 'Status', 'Role', 'Created', 'Actions'])
    
    // Check first row data
    const firstRow = page.locator('tbody tr').first()
    await expect(firstRow.locator('td').nth(0)).toContainText('User 1')
    await expect(firstRow.locator('td').nth(1)).toContainText('user1@example.com')
    
    // Check pagination
    await expect(page.locator('.el-pagination__total')).toContainText('Total 100')
  })
  
  test('searches users correctly', async ({ page }) => {
    // Search for specific user
    await page.fill('[data-testid="search-input"] input', 'User 5')
    await page.press('[data-testid="search-input"] input', 'Enter')
    
    // Wait for search results
    await page.waitForResponse('**/api/users**')
    
    // Should show filtered results
    await expect(page.locator('tbody tr')).toHaveCount(11) // User 5, 15, 25, 35, 45, 50-59
    await expect(page.locator('tbody tr').first().locator('td').nth(0)).toContainText('User 5')
  })
  
  test('sorts table columns', async ({ page }) => {
    // Sort by name column
    await page.click('th:has-text("Name") .caret-wrapper')
    
    // Wait for sort request
    await page.waitForResponse('**/api/users**')
    
    // Check if data is sorted (assuming API handles sorting)
    const firstRowName = await page.locator('tbody tr').first().locator('td').nth(0).textContent()
    expect(firstRowName).toBe('User 1')
  })
  
  test('filters by status', async ({ page }) => {
    // Open status filter
    await page.click('th:has-text("Status") .el-table__column-filter-trigger')
    
    // Select active status
    await page.click('.el-table-filter__list .el-checkbox:has-text("Active")')
    await page.click('.el-table-filter__bottom .el-button--primary')
    
    // Wait for filter request
    await page.waitForResponse('**/api/users**')
    
    // Check that only active users are shown
    const statusCells = page.locator('tbody tr td:nth-child(3)')
    const count = await statusCells.count()
    
    for (let i = 0; i < count; i++) {
      await expect(statusCells.nth(i)).toContainText('active')
    }
  })
  
  test('changes page size', async ({ page }) => {
    // Change page size
    await page.click('.el-pagination__sizes .el-select')
    await page.click('.el-select-dropdown__item:has-text("50 / page")')
    
    // Wait for new data
    await page.waitForResponse('**/api/users**')
    
    // Should show 50 rows
    await expect(page.locator('tbody tr')).toHaveCount(50)
  })
  
  test('navigates between pages', async ({ page }) => {
    // Go to next page
    await page.click('.el-pagination .btn-next')
    
    // Wait for new data
    await page.waitForResponse('**/api/users**')
    
    // Check that different data is loaded
    const firstRowName = await page.locator('tbody tr').first().locator('td').nth(0).textContent()
    expect(firstRowName).toBe('User 21')
    
    // Check pagination state
    await expect(page.locator('.el-pagination__current')).toHaveValue('2')
  })
  
  test('performs bulk operations', async ({ page }) => {
    // Select multiple rows
    await page.click('thead .el-checkbox input')
    
    // Check that all visible rows are selected
    const checkboxes = page.locator('tbody .el-checkbox input')
    const count = await checkboxes.count()
    
    for (let i = 0; i < count; i++) {
      await expect(checkboxes.nth(i)).toBeChecked()
    }
    
    // Perform bulk action
    await page.click('[data-testid="bulk-actions"]')
    await page.click('.el-dropdown-menu__item:has-text("Deactivate Selected")')
    
    // Confirm action
    await page.click('.el-message-box__btns .el-button--primary')
    
    // Should show success message
    await expect(page.locator('.el-message--success')).toContainText('Users deactivated successfully')
  })
})

Multi-step Wizard Navigation

javascript
// tests/e2e/project-setup-wizard.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('Project Setup Wizard', () => {
  test('completes project setup workflow', async ({ page }) => {
    await page.goto('/projects/new')
    
    // Step 1: Basic Information
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('1 of 4')
    await expect(page.locator('.el-steps__step.is-process')).toContainText('Basic Info')
    
    await page.fill('[data-testid="project-name"] input', 'Test Project')
    await page.fill('[data-testid="project-description"] textarea', 'This is a test project description')
    
    await page.click('[data-testid="project-type"]')
    await page.click('.el-select-dropdown__item:has-text("Web Application")')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 2: Team Setup
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('2 of 4')
    await expect(page.locator('.el-steps__step.is-process')).toContainText('Team Setup')
    
    // Add team members
    await page.click('[data-testid="add-member"]')
    await page.fill('[data-testid="member-email-0"] input', 'john@example.com')
    await page.click('[data-testid="member-role-0"]')
    await page.click('.el-select-dropdown__item:has-text("Developer")')
    
    await page.click('[data-testid="add-member"]')
    await page.fill('[data-testid="member-email-1"] input', 'jane@example.com')
    await page.click('[data-testid="member-role-1"]')
    await page.click('.el-select-dropdown__item:has-text("Designer")')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 3: Configuration
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('3 of 4')
    await expect(page.locator('.el-steps__step.is-process')).toContainText('Configuration')
    
    // Select technologies
    await page.check('[data-testid="tech-vue"] input')
    await page.check('[data-testid="tech-typescript"] input')
    await page.check('[data-testid="tech-tailwind"] input')
    
    // Configure deployment
    await page.click('[data-testid="deployment-env"]')
    await page.click('.el-select-dropdown__item:has-text("Production")')
    
    await page.fill('[data-testid="domain"] input', 'testproject.com')
    
    await page.click('[data-testid="next-step"]')
    
    // Step 4: Review
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('4 of 4')
    await expect(page.locator('.el-steps__step.is-process')).toContainText('Review')
    
    // Verify review information
    await expect(page.locator('[data-testid="review-project-name"]')).toContainText('Test Project')
    await expect(page.locator('[data-testid="review-project-type"]')).toContainText('Web Application')
    await expect(page.locator('[data-testid="review-team-count"]')).toContainText('2 members')
    await expect(page.locator('[data-testid="review-technologies"]')).toContainText('Vue, TypeScript, Tailwind')
    
    // Create project
    await page.click('[data-testid="create-project"]')
    
    // Wait for creation
    await page.waitForLoadState('networkidle')
    
    // Should redirect to project dashboard
    await expect(page).toHaveURL(/\/projects\/\d+\/dashboard/)
    await expect(page.locator('h1')).toContainText('Test Project')
  })
  
  test('allows navigation between steps', async ({ page }) => {
    await page.goto('/projects/new')
    
    // Fill step 1
    await page.fill('[data-testid="project-name"] input', 'Test Project')
    await page.click('[data-testid="next-step"]')
    
    // Go back to step 1
    await page.click('[data-testid="prev-step"]')
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('1 of 4')
    
    // Data should be preserved
    await expect(page.locator('[data-testid="project-name"] input')).toHaveValue('Test Project')
    
    // Navigate using step indicator
    await page.click('.el-steps__step:nth-child(3)')
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('3 of 4')
  })
  
  test('validates step completion', async ({ page }) => {
    await page.goto('/projects/new')
    
    // Try to proceed without required fields
    await page.click('[data-testid="next-step"]')
    
    // Should show validation errors
    await expect(page.locator('.el-form-item__error')).toContainText('Please input project name')
    
    // Should not advance to next step
    await expect(page.locator('[data-testid="step-indicator"]')).toContainText('1 of 4')
  })
})

API Integration Testing

CRUD Operations Testing

javascript
// tests/e2e/api-integration.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('API Integration', () => {
  let apiContext
  
  test.beforeAll(async ({ playwright }) => {
    apiContext = await playwright.request.newContext({
      baseURL: 'http://localhost:3001/api'
    })
  })
  
  test.afterAll(async () => {
    await apiContext.dispose()
  })
  
  test('creates, reads, updates, and deletes user', async ({ page }) => {
    await page.goto('/admin/users')
    
    // CREATE: Add new user
    await page.click('[data-testid="add-user"]')
    
    await page.fill('[data-testid="user-name"] input', 'Test User')
    await page.fill('[data-testid="user-email"] input', 'testuser@example.com')
    await page.click('[data-testid="user-role"]')
    await page.click('.el-select-dropdown__item:has-text("User")')
    
    // Intercept create request
    const createResponse = page.waitForResponse('**/api/users', { method: 'POST' })
    await page.click('[data-testid="save-user"]')
    
    const createResult = await createResponse
    expect(createResult.status()).toBe(201)
    
    // Should show success message
    await expect(page.locator('.el-message--success')).toContainText('User created successfully')
    
    // Should close dialog and refresh table
    await expect(page.locator('.el-dialog')).not.toBeVisible()
    
    // READ: Verify user appears in table
    await page.fill('[data-testid="search-input"] input', 'testuser@example.com')
    await page.press('[data-testid="search-input"] input', 'Enter')
    
    await expect(page.locator('tbody tr')).toHaveCount(1)
    await expect(page.locator('tbody tr td').nth(0)).toContainText('Test User')
    await expect(page.locator('tbody tr td').nth(1)).toContainText('testuser@example.com')
    
    // UPDATE: Edit user
    await page.click('[data-testid="edit-user"]')
    
    await page.fill('[data-testid="user-name"] input', 'Updated Test User')
    await page.click('[data-testid="user-role"]')
    await page.click('.el-select-dropdown__item:has-text("Admin")')
    
    const updateResponse = page.waitForResponse('**/api/users/*', { method: 'PUT' })
    await page.click('[data-testid="save-user"]')
    
    const updateResult = await updateResponse
    expect(updateResult.status()).toBe(200)
    
    // Verify update
    await expect(page.locator('tbody tr td').nth(0)).toContainText('Updated Test User')
    await expect(page.locator('tbody tr td').nth(3)).toContainText('Admin')
    
    // DELETE: Remove user
    await page.click('[data-testid="delete-user"]')
    
    // Confirm deletion
    await page.click('.el-message-box__btns .el-button--primary')
    
    const deleteResponse = page.waitForResponse('**/api/users/*', { method: 'DELETE' })
    const deleteResult = await deleteResponse
    expect(deleteResult.status()).toBe(200)
    
    // Verify user is removed
    await expect(page.locator('tbody tr')).toHaveCount(0)
    await expect(page.locator('.el-empty')).toContainText('No data')
  })
  
  test('handles API errors gracefully', async ({ page }) => {
    // Mock server error
    await page.route('**/api/users', route => {
      if (route.request().method() === 'POST') {
        route.fulfill({
          status: 500,
          contentType: 'application/json',
          body: JSON.stringify({ error: 'Internal server error' })
        })
      } else {
        route.continue()
      }
    })
    
    await page.goto('/admin/users')
    
    // Try to create user
    await page.click('[data-testid="add-user"]')
    await page.fill('[data-testid="user-name"] input', 'Test User')
    await page.fill('[data-testid="user-email"] input', 'test@example.com')
    await page.click('[data-testid="save-user"]')
    
    // Should show error message
    await expect(page.locator('.el-message--error')).toContainText('Failed to create user')
    
    // Dialog should remain open
    await expect(page.locator('.el-dialog')).toBeVisible()
  })
  
  test('handles network timeouts', async ({ page }) => {
    // Mock slow response
    await page.route('**/api/users', route => {
      setTimeout(() => {
        route.fulfill({
          status: 200,
          contentType: 'application/json',
          body: JSON.stringify({ data: [], total: 0 })
        })
      }, 10000) // 10 second delay
    })
    
    await page.goto('/admin/users')
    
    // Should show loading state
    await expect(page.locator('.el-loading-mask')).toBeVisible()
    
    // Should timeout and show error
    await expect(page.locator('.el-message--error')).toContainText('Request timeout', { timeout: 15000 })
  })
})

Real-time Features Testing

WebSocket Integration

javascript
// tests/e2e/real-time-chat.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('Real-time Chat', () => {
  test('sends and receives messages in real-time', async ({ browser }) => {
    // Create two browser contexts for two users
    const context1 = await browser.newContext()
    const context2 = await browser.newContext()
    
    const page1 = await context1.newPage()
    const page2 = await context2.newPage()
    
    // User 1 joins chat
    await page1.goto('/chat/room/123')
    await page1.fill('[data-testid="username"] input', 'User1')
    await page1.click('[data-testid="join-chat"]')
    
    // User 2 joins chat
    await page2.goto('/chat/room/123')
    await page2.fill('[data-testid="username"] input', 'User2')
    await page2.click('[data-testid="join-chat"]')
    
    // Wait for both users to be connected
    await expect(page1.locator('[data-testid="user-list"]')).toContainText('User1')
    await expect(page1.locator('[data-testid="user-list"]')).toContainText('User2')
    await expect(page2.locator('[data-testid="user-list"]')).toContainText('User1')
    await expect(page2.locator('[data-testid="user-list"]')).toContainText('User2')
    
    // User 1 sends a message
    await page1.fill('[data-testid="message-input"] input', 'Hello from User1!')
    await page1.click('[data-testid="send-message"]')
    
    // Both users should see the message
    await expect(page1.locator('[data-testid="message-list"]')).toContainText('User1: Hello from User1!')
    await expect(page2.locator('[data-testid="message-list"]')).toContainText('User1: Hello from User1!')
    
    // User 2 responds
    await page2.fill('[data-testid="message-input"] input', 'Hi there, User1!')
    await page2.click('[data-testid="send-message"]')
    
    // Both users should see the response
    await expect(page1.locator('[data-testid="message-list"]')).toContainText('User2: Hi there, User1!')
    await expect(page2.locator('[data-testid="message-list"]')).toContainText('User2: Hi there, User1!')
    
    // Test typing indicators
    await page1.fill('[data-testid="message-input"] input', 'I am typing...')
    
    // User 2 should see typing indicator
    await expect(page2.locator('[data-testid="typing-indicator"]')).toContainText('User1 is typing...')
    
    // Clear input - typing indicator should disappear
    await page1.fill('[data-testid="message-input"] input', '')
    await expect(page2.locator('[data-testid="typing-indicator"]')).not.toBeVisible()
    
    // Test user leaving
    await page1.close()
    
    // User 2 should see that User 1 left
    await expect(page2.locator('[data-testid="user-list"]')).not.toContainText('User1')
    await expect(page2.locator('[data-testid="message-list"]')).toContainText('User1 left the chat')
    
    await context1.close()
    await context2.close()
  })
})

Performance Integration Testing

Load Testing with Multiple Users

javascript
// tests/e2e/performance.spec.js (Playwright)
import { test, expect } from '@playwright/test'

test.describe('Performance Tests', () => {
  test('handles multiple concurrent users', async ({ browser }) => {
    const contexts = []
    const pages = []
    
    // Create 10 concurrent users
    for (let i = 0; i < 10; i++) {
      const context = await browser.newContext()
      const page = await context.newPage()
      contexts.push(context)
      pages.push(page)
    }
    
    // All users navigate to the same page simultaneously
    const navigationPromises = pages.map((page, index) => 
      page.goto(`/dashboard?user=${index + 1}`)
    )
    
    await Promise.all(navigationPromises)
    
    // All users should load successfully
    for (const page of pages) {
      await expect(page.locator('h1')).toContainText('Dashboard')
    }
    
    // All users perform actions simultaneously
    const actionPromises = pages.map(async (page, index) => {
      await page.click('[data-testid="load-data"]')
      await page.waitForLoadState('networkidle')
      return page.locator('[data-testid="data-count"]').textContent()
    })
    
    const results = await Promise.all(actionPromises)
    
    // All requests should succeed
    results.forEach(result => {
      expect(parseInt(result)).toBeGreaterThan(0)
    })
    
    // Clean up
    for (const context of contexts) {
      await context.close()
    }
  })
  
  test('measures page load performance', async ({ page }) => {
    // Start performance measurement
    await page.goto('/dashboard')
    
    // Measure Core Web Vitals
    const metrics = await page.evaluate(() => {
      return new Promise((resolve) => {
        new PerformanceObserver((list) => {
          const entries = list.getEntries()
          const lcp = entries.find(entry => entry.entryType === 'largest-contentful-paint')
          if (lcp) {
            resolve({
              lcp: lcp.startTime,
              fcp: performance.getEntriesByType('paint').find(entry => entry.name === 'first-contentful-paint')?.startTime,
              domContentLoaded: performance.getEntriesByType('navigation')[0]?.domContentLoadedEventEnd
            })
          }
        }).observe({ entryTypes: ['largest-contentful-paint'] })
      })
    })
    
    // Assert performance thresholds
    expect(metrics.lcp).toBeLessThan(2500) // LCP should be under 2.5s
    expect(metrics.fcp).toBeLessThan(1800) // FCP should be under 1.8s
    expect(metrics.domContentLoaded).toBeLessThan(1500) // DOM ready should be under 1.5s
  })
})

Best Practices

1. Test Organization

  • Group related tests in describe blocks
  • Use descriptive test names
  • Set up proper test data and cleanup
  • Use page objects for complex interactions

2. API Testing

  • Mock external dependencies
  • Test both success and error scenarios
  • Verify request/response data
  • Test timeout and retry logic

3. UI Integration

  • Test complete user workflows
  • Verify state changes across components
  • Test responsive behavior
  • Validate accessibility features

4. Performance

  • Set performance budgets
  • Test with realistic data volumes
  • Monitor memory usage
  • Test concurrent user scenarios

5. Reliability

  • Use proper wait strategies
  • Handle flaky tests with retries
  • Clean up test data
  • Use stable selectors

Integration testing ensures that your Element Plus application works correctly as a complete system, providing confidence in the user experience and system reliability.

Element Plus Study Guide