【Vue】E2E测试(Cypress/Playwright)

在这里插入图片描述

个人主页:Guiat
归属专栏:Vue

在这里插入图片描述

正文

1. Vue E2E测试简介

端到端(E2E)测试是验证Vue应用整体功能的方法,它通过模拟真实用户与应用的交互来测试完整的用户流程。

1.1 E2E测试的重要性

  • 验证应用从前端到后端的完整功能流程
  • 发现集成过程中产生的问题
  • 测试真实用户场景和体验
  • 确保业务关键流程的正确性

1.2 E2E测试工具对比

特性CypressPlaywright
发布时间2017年2020年
开发商Cypress.ioMicrosoft
支持的浏览器Chrome, Firefox, Edge, ElectronChrome, Firefox, Safari, Edge
语言支持JavaScript/TypeScriptJavaScript/TypeScript, Python, Java, .NET
并行测试付费版支持原生支持
调试体验可视化时间轴, 即时重载丰富的调试工具, 代码生成
社区活跃度非常活跃快速增长

2. Cypress 基础

Cypress是一个专为现代web应用设计的E2E测试框架,提供友好的开发体验和强大的调试功能。

2.1 Cypress 安装与设置

2.1.1 安装Cypress

# 在Vue项目中安装
npm install cypress --save-dev

# 或使用Vue CLI插件
vue add e2e-cypress

2.1.2 配置Cypress

创建或编辑cypress.config.js文件:

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:8080',
    specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
    viewportWidth: 1280,
    viewportHeight: 720
  },
});

2.1.3 基本目录结构

cypress/
├── e2e/            # 测试文件
├── fixtures/       # 测试数据
├── support/        # 辅助命令和配置
│   ├── commands.js
│   └── e2e.js
└── videos/         # 测试录制的视频

2.2 编写第一个Cypress测试

创建cypress/e2e/home.cy.js文件:

describe('首页测试', () => {
  beforeEach(() => {
    // 每个测试前访问首页
    cy.visit('/')
  })

  it('应该显示网站标题', () => {
    cy.get('h1').should('contain', 'Vue应用')
  })

  it('导航菜单应该包含正确的链接', () => {
    cy.get('nav')
      .find('a')
      .should('have.length.at.least', 2)
    
    cy.get('nav').contains('首页')
    cy.get('nav').contains('关于我们')
  })
})

2.3 运行Cypress测试

# 打开Cypress测试运行器UI
npx cypress open

# 或直接在命令行运行所有测试
npx cypress run

3. Cypress 核心概念

3.1 Cypress命令与断言

3.1.1 基本命令

// 导航到URL
cy.visit('/login')

// 选择元素
cy.get('#username')

// 输入文本
cy.get('#username').type('testuser')

// 点击元素
cy.get('button').click()

// 等待特定时间
cy.wait(1000) // 等待1秒

// 等待特定请求
cy.intercept('GET', '/api/users').as('getUsers')
cy.wait('@getUsers')

3.1.2 常用断言

// 检查文本内容
cy.get('h1').should('contain', '欢迎登录')

// 检查可见性
cy.get('.error-message').should('be.visible')

// 检查元素数量
cy.get('li').should('have.length', 5)

// 检查属性值
cy.get('input').should('have.attr', 'placeholder', '请输入用户名')

// 检查CSS类
cy.get('button').should('have.class', 'active')

// 链式断言
cy.get('button')
  .should('be.enabled')
  .and('have.css', 'background-color', 'rgb(0, 123, 255)')

3.2 处理异步操作

Cypress命令是异步的,但它自动处理等待和重试机制。

// Cypress会自动等待元素出现
cy.get('.dynamic-content', { timeout: 10000 }).should('be.visible')

// 等待网络请求
cy.intercept('POST', '/api/login').as('loginRequest')
cy.get('#login-button').click()
cy.wait('@loginRequest')
  .its('response.statusCode')
  .should('eq', 200)

// 等待Vue组件渲染完成
cy.get('[data-cy=component]').should('exist')

3.3 网络请求模拟

3.3.1 拦截与监听请求

// 监听API调用
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers')

// 模拟API响应
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers')

// 动态响应
cy.intercept('GET', '/api/users', (req) => {
  req.reply({
    statusCode: 200,
    body: {
      users: [
        { id: 1, name: '张三' },
        { id: 2, name: '李四' }
      ]
    }
  })
})

3.3.2 模拟错误状态

// 模拟服务器错误
cy.intercept('POST', '/api/submit', {
  statusCode: 500,
  body: { error: '服务器内部错误' }
}).as('submitForm')

cy.get('form').submit()
cy.wait('@submitForm')
cy.get('.error-message').should('be.visible')

4. Cypress进阶技巧

4.1 自定义命令

cypress/support/commands.js中添加:

// 登录命令
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login')
  cy.get('#username').type(username)
  cy.get('#password').type(password)
  cy.get('button[type="submit"]').click()
  cy.url().should('include', '/dashboard')
})

// 检查Toast消息
Cypress.Commands.add('checkToast', (message) => {
  cy.get('.toast-message')
    .should('be.visible')
    .and('contain', message)
})

// 添加商品到购物车
Cypress.Commands.add('addToCart', (productId) => {
  cy.get(`[data-product-id="${productId}"]`)
    .find('.add-to-cart')
    .click()
  cy.get('.cart-count').should('not.contain', '0')
})

在测试中使用自定义命令:

describe('购物车测试', () => {
  beforeEach(() => {
    cy.login('testuser', 'password123')
  })

  it('应该能将商品添加到购物车', () => {
    cy.visit('/products')
    cy.addToCart(1)
    cy.checkToast('商品已添加到购物车')
  })
})

4.2 测试Vue组件

4.2.1 组件数据测试属性

在Vue组件中添加data-cy属性:

<template>
  <div>
    <input 
      data-cy="search-input"
      v-model="searchTerm"
      placeholder="搜索..."
    />
    <button data-cy="search-button" @click="search">搜索</button>
    <div v-if="loading" data-cy="loading-indicator">加载中...</div>
    <ul v-else>
      <li 
        v-for="result in results" 
        :key="result.id"
        data-cy="search-result"
      >
        {{ result.name }}
      </li>
    </ul>
  </div>
</template>

在测试中使用这些属性:

describe('搜索组件', () => {
  beforeEach(() => {
    cy.visit('/search')
  })

  it('成功搜索并展示结果', () => {
    // 拦截API请求
    cy.intercept('GET', '/api/search*', {
      results: [
        { id: 1, name: '搜索结果1' },
        { id: 2, name: '搜索结果2' }
      ]
    }).as('searchRequest')

    // 执行搜索
    cy.get('[data-cy=search-input]').type('测试')
    cy.get('[data-cy=search-button]').click()
    
    // 检查加载状态
    cy.get('[data-cy=loading-indicator]').should('be.visible')
    cy.wait('@searchRequest')
    cy.get('[data-cy=loading-indicator]').should('not.exist')
    
    // 验证结果显示
    cy.get('[data-cy=search-result]').should('have.length', 2)
    cy.get('[data-cy=search-result]').first().should('contain', '搜索结果1')
  })
})

4.2.2 测试Vuex状态

// 测试与Vuex交互的组件
describe('Todo组件', () => {
  it('应该通过Vuex添加新待办事项', () => {
    cy.visit('/todos')
    
    // 监听Vuex action
    cy.window().then((win) => {
      cy.spy(win.store, 'dispatch').as('addTodoAction')
    })
    
    // 添加待办事项
    cy.get('[data-cy=new-todo]').type('学习E2E测试{enter}')
    
    // 验证Vuex action被调用
    cy.get('@addTodoAction').should('be.calledWith', 'addTodo', {
      text: '学习E2E测试',
      completed: false
    })
    
    // 验证UI更新
    cy.get('[data-cy=todo-item]').should('contain', '学习E2E测试')
  })
})

4.3 测试文件上传

describe('文件上传', () => {
  it('应该上传文件并显示预览', () => {
    cy.visit('/upload')
    
    // 准备文件并上传
    cy.get('input[type=file]').attachFile({
      filePath: 'test-image.jpg',
      mimeType: 'image/jpeg',
      encoding: 'base64'
    })
    
    // 验证上传成功
    cy.get('.file-preview').should('be.visible')
    cy.get('.file-name').should('contain', 'test-image.jpg')
  })
})

5. Playwright 基础

Playwright是由Microsoft开发的较新的E2E测试工具,支持多种浏览器和编程语言。

5.1 Playwright 安装与设置

5.1.1 安装Playwright

# 在Vue项目中安装
npm init playwright@latest

# 或使用Vue CLI插件
vue add e2e-playwright

安装过程中会提示选择浏览器、测试位置等选项。

5.1.2 配置Playwright

编辑playwright.config.js文件:

// @ts-check
const { defineConfig, devices } = require('@playwright/test');

module.exports = defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:8080',
    screenshot: 'only-on-failure',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

5.1.3 基本目录结构

tests/
├── e2e/           # 测试文件
│   └── example.spec.js
├── fixtures/      # 测试数据
└── support/       # 辅助函数和配置

5.2 编写第一个Playwright测试

创建tests/e2e/home.spec.js文件:

const { test, expect } = require('@playwright/test');

test.describe('首页测试', () => {
  test.beforeEach(async ({ page }) => {
    // 每个测试前访问首页
    await page.goto('/');
  });

  test('应该显示网站标题', async ({ page }) => {
    await expect(page.locator('h1')).toContainText('Vue应用');
  });

  test('导航菜单应该包含正确的链接', async ({ page }) => {
    const navLinks = page.locator('nav a');
    await expect(navLinks).toHaveCount(3);
    await expect(page.locator('nav')).toContainText('首页');
    await expect(page.locator('nav')).toContainText('关于我们');
  });
});

5.3 运行Playwright测试

# 运行所有测试
npx playwright test

# 在UI模式下运行测试
npx playwright test --ui

# 只在特定浏览器中运行
npx playwright test --project=chromium

# 运行单个测试文件
npx playwright test home.spec.js

6. Playwright 核心概念

6.1 定位元素与断言

6.1.1 定位元素

// 使用CSS选择器
const button = page.locator('button.submit');

// 使用文本内容
const loginLink = page.getByText('登录');

// 使用role
const submitButton = page.getByRole('button', { name: '提交' });

// 使用标签
const usernameInput = page.getByLabel('用户名');

// 使用测试ID (推荐)
const searchBox = page.getByTestId('search-input');

// 组合定位
const item = page.locator('.list-item').filter({ hasText: '项目3' });

6.1.2 常用断言

// 检查文本内容
await expect(page.locator('h1')).toContainText('欢迎');

// 检查可见性
await expect(page.locator('.error')).toBeVisible();

// 检查元素数量
await expect(page.locator('li')).toHaveCount(5);

// 检查属性
await expect(page.locator('input')).toHaveAttribute('placeholder', '搜索');

// 检查CSS类
await expect(page.locator('button')).toHaveClass(/active/);

// 检查页面标题
await expect(page).toHaveTitle(/Vue应用/);

// 检查URL
await expect(page).toHaveURL(/\/dashboard/);

6.2 交互与等待

// 点击元素
await page.locator('button').click();

// 填写文本
await page.locator('#username').fill('testuser');

// 选择下拉选项
await page.locator('select').selectOption('选项1');

// 检查复选框
await page.locator('input[type="checkbox"]').check();

// 鼠标悬停
await page.locator('.dropdown').hover();

// 键盘操作
await page.locator('input').press('Enter');

// 等待元素
await page.locator('.dynamic-content').waitFor({ state: 'visible' });

// 等待导航
await Promise.all([
  page.waitForNavigation(),
  page.locator('a').click()
]);

// 等待网络请求
const responsePromise = page.waitForResponse('**/api/users');
await page.locator('button').click();
const response = await responsePromise;
expect(response.status()).toBe(200);

6.3 网络请求模拟

6.3.1 拦截与模拟请求

// 拦截请求并提供模拟响应
await page.route('**/api/users', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([
      { id: 1, name: '张三' },
      { id: 2, name: '李四' }
    ])
  });
});

// 拦截并阻止某些请求
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

// 修改请求
await page.route('**/api/data', async route => {
  const request = route.request();
  const postData = JSON.parse(request.postData() || '{}');
  postData.additionalParam = 'value';
  
  await route.continue({
    postData: JSON.stringify(postData)
  });
});

6.3.2 监听网络请求

// 监听所有网络请求
page.on('request', request => {
  console.log(`>> ${request.method()} ${request.url()}`);
});

page.on('response', response => {
  console.log(`<< ${response.status()} ${response.url()}`);
});

// 等待特定请求完成
const [response] = await Promise.all([
  page.waitForResponse('**/api/login'),
  page.locator('#login-button').click()
]);

expect(response.status()).toBe(200);
const body = await response.json();
expect(body.success).toBeTruthy();

7. Playwright 进阶技巧

7.1 Playwright 测试挂钩和夹具

const { test, expect } = require('@playwright/test');

// 创建测试夹具 (fixture)
test.beforeAll(async ({ browser }) => {
  // 在所有测试前执行一次
  const context = await browser.newContext();
  const page = await context.newPage();
  
  // 登录并保存状态
  await page.goto('/login');
  await page.fill('#username', 'admin');
  await page.fill('#password', 'password');
  await page.click('button[type="submit"]');
  await page.waitForURL('/dashboard');
  
  // 保存认证状态
  await context.storageState({ path: './auth.json' });
});

// 使用已认证状态的夹具
test.use({ storageState: './auth.json' });

test('已登录用户可以查看仪表板', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.locator('h1')).toContainText('欢迎回来');
});

7.2 自定义工具函数

创建tests/support/helpers.js

// 等待Vue组件挂载完成
async function waitForVueReady(page) {
  await page.waitForFunction(() => {
    return window.__VUE_DEVTOOLS_GLOBAL_HOOK__?.Vue?.nextTick !== undefined;
  });
}

// 检查Vuex状态
async function getVuexState(page) {
  return page.evaluate(() => {
    return window.$store.state;
  });
}

// 切换深色/浅色模式
async function toggleDarkMode(page) {
  await page.locator('[data-testid="theme-toggle"]').click();
  // 等待CSS变化应用
  await page.waitForTimeout(100);
}

module.exports = {
  waitForVueReady,
  getVuexState,
  toggleDarkMode
};

在测试中使用:

const { test, expect } = require('@playwright/test');
const { waitForVueReady, getVuexState } = require('../support/helpers');

test('测试深色模式切换', async ({ page }) => {
  await page.goto('/');
  await waitForVueReady(page);
  
  // 获取初始状态
  const initialState = await getVuexState(page);
  expect(initialState.theme).toBe('light');
  
  // 切换主题
  await page.locator('[data-testid="theme-toggle"]').click();
  
  // 验证状态变化
  const updatedState = await getVuexState(page);
  expect(updatedState.theme).toBe('dark');
  
  // 验证CSS变化
  await expect(page.locator('body')).toHaveClass(/dark-theme/);
});

7.3 测试视觉对比

const { test, expect } = require('@playwright/test');

test('首页视觉无回归', async ({ page }) => {
  await page.goto('/');
  
  // 等待内容完全加载
  await page.waitForSelector('.main-content', { state: 'visible' });
  
  // 截屏并与基准图进行比较
  await expect(page).toHaveScreenshot('home-page.png');
});

test('组件视觉无回归', async ({ page }) => {
  await page.goto('/components');
  
  // 针对特定组件截屏
  await expect(page.locator('.card-component')).toHaveScreenshot('card.png');
});

8. 测试真实场景与用户流程

8.1 完整注册-登录-购买流程测试

使用Cypress:

describe('电商购买流程', () => {
  it('新用户应该能注册、登录并完成购买', () => {
    // 随机生成用户信息
    const email = `test${Date.now()}@example.com`;
    const password = 'Password123!';
    
    // 1. 注册流程
    cy.visit('/register');
    cy.get('[data-cy=username]').type(`user${Date.now()}`);
    cy.get('[data-cy=email]').type(email);
    cy.get('[data-cy=password]').type(password);
    cy.get('[data-cy=confirm-password]').type(password);
    cy.get('[data-cy=register-button]').click();
    cy.url().should('include', '/login');
    
    // 2. 登录流程
    cy.get('[data-cy=email]').type(email);
    cy.get('[data-cy=password]').type(password);
    cy.get('[data-cy=login-button]').click();
    cy.url().should('include', '/dashboard');
    
    // 3. 浏览商品
    cy.visit('/products');
    cy.get('[data-cy=product-card]').first().click();
    cy.url().should('include', '/product/');
    
    // 4. 添加到购物车
    cy.get('[data-cy=add-to-cart]').click();
    cy.get('[data-cy=cart-count]').should('contain', '1');
    
    // 5. 查看购物车
    cy.get('[data-cy=cart-icon]').click();
    cy.url().should('include', '/cart');
    cy.get('[data-cy=cart-item]').should('have.length', 1);
    
    // 6. 结账流程
    cy.get('[data-cy=checkout-button]').click();
    cy.url().should('include', '/checkout');
    
    // 7. 填写配送信息
    cy.get('[data-cy=address]').type('测试地址');
    cy.get('[data-cy=city]').type('北京');
    cy.get('[data-cy=zip]').type('100000');
    cy.get('[data-cy=phone]').type('13800138000');
    
    // 8. 选择支付方式
    cy.get('[data-cy=payment-method]').select('支付宝');
    
    // 9. 提交订单
    cy.intercept('POST', '/api/orders').as('createOrder');
    cy.get('[data-cy=place-order]').click();
    cy.wait('@createOrder');
    
    // 10. 确认订单成功
    cy.url().should('include', '/order-confirmation');
    cy.get('[data-cy=order-success]').should('be.visible');
    cy.get('[data-cy=order-number]').should('not.be.empty');
  });
});

使用Playwright:

const { test, expect } = require('@playwright/test');

test('完整电商购买流程', async ({ page }) => {
  // 随机生成用户信息
  const email = `test${Date.now()}@example.com`;
  const password = 'Password123!';
  
  // 1. 注册流程
  await page.goto('/register');
  await page.fill('[data-testid="username"]', `user${Date.now()}`);
  await page.fill('[data-testid="email"]', email);
  await page.fill('[data-testid="password"]', password);
  await page.fill('[data-testid="confirm-password"]', password);
  
  await Promise.all([
    page.waitForNavigation(),
    page.click('[data-testid="register-button"]')
  ]);
  
  expect(page.url()).toContain('/login');
  
  // 2. 登录流程
  await page.fill('[data-testid="email"]', email);
  await page.fill('[data-testid="password"]', password);
  
  await Promise.all([
    page.waitForNavigation(),
    page.click('[data-testid="login-button"]')
  ]);
  
  expect(page.url()).toContain('/dashboard');
  
  // 3~10. (与Cypress示例类似的后续流程)
  // ...
});

8.2 测试响应式布局

使用Cypress:

describe('响应式布局测试', () => {
  it('在手机视图中应正确显示导航菜单', () => {
    // 设置手机视口
    cy.viewport('iphone-x');
    cy.visit('/');
    
    // 验证菜单按钮存在
    cy.get('[data-cy=mobile-menu-button]').should('be.visible');
    cy.get('nav').should('not.be.visible');
    
    // 打开菜单
    cy.get('[data-cy=mobile-menu-button]').click();
    cy.get('nav').should('be.visible');
    
    // 验证菜单项
    cy.get('nav a').should('have.length.at.least', 3);
  });
  
  it('在桌面视图中应直接显示导航栏', () => {
    // 设置桌面视口
    cy.viewport(1280, 720);
    cy.visit('/');
    
    // 验证菜单按钮不存在或不可见
    cy.get('[data-cy=mobile-menu-button]').should('not.be.visible');
    
    // 导航栏直接可见
    cy.get('nav').should('be.visible');
    cy.get('nav a').should('have.length.at.least', 3);
  });
});

使用Playwright:

const { test, expect } = require('@playwright/test');
const { devices } = require('@playwright/test');

// 手机视图测试
test.use({ viewport: devices['iPhone X'].viewport });

test('在手机视图中测试导航菜单', async ({ page }) => {
  await page.goto('/');
  
  // 验证菜单按钮存在
  const menuButton = page.locator('[data-testid="mobile-menu-button"]');
  await expect(menuButton).toBeVisible();
  
  // 验证导航菜单默认隐藏
  const nav = page.locator('nav');
  await expect(nav).not.toBeVisible();
  
  // 打开菜单
  await menuButton.click();
  await expect(nav).toBeVisible();
});

// 桌面视图测试
test.describe('桌面视图测试', () => {
  test.use({ viewport: { width: 1280, height: 720 } });
  
  test('桌面视图应直接显示导航栏', async ({ page }) => {
    await page.goto('/');
    
    // 验证菜单按钮不可见
    const menuButton = page.locator('[data-testid="mobile-menu-button"]');
    await expect(menuButton).not.toBeVisible();
    
    // 导航栏直接可见
    const nav = page.locator('nav');
    await expect(nav).toBeVisible();
  });
});

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

### 如何在 UniApp 中使用 Vue3 和 Vite #### 配置说明 UniApp 是一个多端开发框架,支持通过 Vue 进行跨平台应用开发。Vue3 的引入以及与 Vite 的集成能够显著提升项目的性能和开发体验。以下是关于如何配置 UniApp 使用 Vue3 和 Vite 的详细指导。 --- #### 1. 初始化项目并升级到 Vue3 为了使 UniApp 支持 Vue3,需要先初始化一个基础的 UniApp 项目,并将其升级至 Vue3 版本。具体操作如下: - 创建一个新的 UniApp 项目: ```bash npm init @dcloudio/uni-app my-project-name cd my-project-name ``` - 修改 `manifest.json` 文件中的设置项,在 `"mp-vue"` 或其他目标平台上启用 Vue3 支持[^1]。 - 升级核心依赖包以兼容 Vue3 功能: ```bash npm install vue@next vue-router@next unplugin-vue-components --save-dev ``` 上述命令会安装最新的 Vue3 及其路由插件版本,同时加入组件按需加载的支持工具。 --- #### 2. 集成 Vite 构建工具 Vite 提供了一种更快捷高效的构建方式,适合现代前端开发需求。要将 Vite 应用于 UniApp,请按照以下步骤执行: - 安装必要的 Vite 插件和支持库: ```bash npm install vite-plugin-uniapp vite -D ``` - 在项目根目录创建或修改现有的 `vite.config.ts` 文件,添加以下内容: ```typescript import { defineConfig } from 'vite'; import uni from '@dcloudio/vite-plugin-uni'; export default defineConfig({ plugins: [ uni(), ], }); ``` 此配置文件启用了官方推荐的 Vite 插件来处理 UniApp 的特殊编译逻辑[^3]。 --- #### 3. 解决环境变量注入问题 如果希望在整个项目生命周期中共享 `.env.*` 类型的环境变量,则可借助第三方模块完成统一管理。例如,基于 Webpack 的场景通常采用 `dotenv-webpack` 实现全局化读取;而在 Vite 场景下则无需额外插件即可实现类似功能[^2]。 只需确保所有的敏感数据都存储于对应的 `.env.production` 或者 `.env.development` 文件之中,并遵循命名规则(如 `VITE_APP_KEY=value`),这些值会被自动挂载到客户端代码下的 `import.meta.env` 对象里供随时调用。 --- #### 4. 测试驱动开发 (TDD/E2E Testing) 对于大型复杂业务而言,实施全面覆盖的功能性和回归测试至关重要。考虑到 UniApp 跨多终端的特点,建议选用 Cypress.io 或 Playwright 等现代化 E2E 工具替代传统 Selenium Server 方案[^4]。 下面是一个简单的示例脚本片段展示如何验证页面交互行为是否正常工作: ```javascript describe('Login Page Test Suite', () => { it('should display login form correctly', () => { cy.visit('/login'); cy.get('#username').type('testUser'); cy.contains('button', 'Submit').click(); cy.url().should('include', '/dashboard'); }); }); ``` 此外还可以考虑 PhantomJS 来捕获特定时刻的画面截图作为辅助手段之一[^5]。 --- ### 总结 综上所述,通过合理调整现有架构并将最新技术栈融入其中,完全可以顺利达成 UniApp 结合 Vue3 加速渲染速度的同时利用 Vite 增强整体灵活性的目标!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guiat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值