You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Playwright测试触发403错误但手动操作正常的排查求助

生产环境中Playwright自动化测试交互按钮失败,手动操作正常的问题排查

问题描述

在生产环境网站中,Playwright自动化测试点击注册、加入购物车等交互按钮时失败,但手动执行相同步骤可正常工作。例如注册新用户的测试始终失败,手动注册却无异常。

测试执行步骤:

  1. 访问网站
  2. 点击用户图标
  3. 在注册弹窗中输入待创建的用户邮箱
  4. 点击Continue按钮

现象详情

  • 网站UI提示:An error ocurred, please try again
  • 控制台报错:Failed to load resource: the server responded with a status of 403 /api/account/profile/auth-options:1
  • 网络面板显示:auth-options接口请求返回403 Forbidden,提示无访问权限
  • 测试因登录函数调用后超时失败

测试代码与配置

测试代码

import { test, expect } from '@playwright/test';
import { LandingPage } from '../pageObjects/landingPage';
import { LoginModalPage } from '../pageObjects/loginModalPage';

test.describe('test suite', () => {
  test.beforeEach(async({ page }) => {
    const landingPage = new LandingPage(page);
    await landingPage.goToLandingPage();
  }); 

  test('add product to the cart', async ({ page }) => {
    const landingPage = new LandingPage(page);
    const loginModal = new LoginModalPage(page);

    await landingPage.customerInfoButton.click();
    await loginModal.login('test@test.com', 'password');
 
    await landingPage.logout(); 
  });
});

Page Object代码

LoginModalPage

import { Page, Locator } from '@playwright/test'

export class LoginModalPage {
    readonly page: Page;
    readonly loginModal: Locator;
    readonly emailInputField: Locator;
    readonly contactDataCheckbox: Locator;
    readonly termsAndConditionsCheckbox: Locator;
    readonly continueButton: Locator;

    constructor(page: Page){
        this.page = page;
        this.loginModal = page.getByTestId('landing-screen');
        this.emailInputField = this.loginModal.getByRole('textbox', {name: 'email address'});
        this.contactDataCheckbox = this.loginModal.locator('.gl-checkbox__label', {hasText: 'contact data'});
        this.termsAndConditionsCheckbox = this.loginModal.locator('.gl-checkbox__label', {hasText: 'Register'});
        this.continueButton = this.loginModal.getByRole('button', {name: 'Continue'});
    }

    async login(user: string, password: string){
        await this.emailInputField.pressSequentially(user, {delay: 50});
        await this.contactDataCheckbox.click();
        await this.termsAndConditionsCheckbox.click();
        await this.continueButton.click();
    }
}

LandingPage

import { Page, expect, Locator } from '@playwright/test'

export class LandingPage {
    readonly page: Page;
    readonly cookiesButton: Locator;
    readonly heading: Locator;
    readonly customerInfoButton: Locator;
    readonly accountTab: Locator;

    constructor(page: Page){
        this.page = page;
        this.cookiesButton = page.getByRole('button', { name: 'accept cookies'});
        this.heading = page.getByRole('heading', { name: 'buy now' });
        this.customerInfoButton = page.getByTestId('customer-info-button');
        this.accountTab = page.getByTestId('home-account-tab');
    }

    async goToLandingPage(){
        await this.page.goto('/'); 
    }
    
    async checkTitleAndHeaderPage(){
        await expect(this.page).toHaveTitle(/official site/);
        await expect(this.heading).toBeVisible(); 
    }
}

Playwright配置

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: false,
  retries: process.env.CI ? 2 : 2,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: websiteURL,
    testIdAttribute: 'data-auto-id', 
    trace: 'on', 
  },

  projects: [
    {
      name: 'chromium',
      use: { 
        ...devices['Desktop Chrome'] 
      },
    },
     {
       name: 'Google Chrome',
       use: { ...devices['Desktop Chrome'], channel: 'chrome' },
     },
  ],

});

已尝试的解决方法

  • 更换测试输入数据
  • 在不同浏览器中运行测试
  • 添加硬等待
  • 清理Cookie(未生效)
  • 在代码中设置User Agent
  • 对比测试与手动请求的Header差异:
    • Referrer Policy:测试中为no-referrer,手动操作为strict-origin-when-cross-origin
    • content-type响应头:测试中为text/html,手动操作为application/json; charset=utf-8
    • Referer:测试中为空,手动操作为对应URL/endpoint
    • Sec-Ch-Ua、Sec-Ch-Ua-Platform、User-Agent字段均存在差异

解决建议

  1. 对齐请求头参数

    • 在Playwright页面初始化时设置正确的Referrer Policy:
      await page.setExtraHTTPHeaders({
        'Referrer-Policy': 'strict-origin-when-cross-origin'
      });
      
    • 拦截auth-options接口请求,修正关键头部信息:
      await page.route('/api/account/profile/auth-options', async (route) => {
        const originalHeaders = route.request().headers();
        await route.continue({
          headers: {
            ...originalHeaders,
            'Referer': 'https://你的生产域名/', // 替换为手动请求的Referer值
            'Content-Type': 'application/json; charset=utf-8'
          }
        });
      });
      
  2. 完全匹配浏览器标识

    • 复制手动操作时的完整User-Agent字符串,配置到Playwright中:
      // 在playwright.config.js的use字段添加
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
      
    • 若Sec-Ch系列头部仍不匹配,通过初始化脚本模拟:
      await page.addInitScript(() => {
        Object.defineProperty(navigator, 'userAgentData', {
          value: {
            brands: [{brand: 'Chromium', version: '118'}, {brand: 'Google Chrome', version: '118'}, {brand: 'Not=A?Brand', version: '99'}],
            platform: 'Windows'
          }
        });
      });
      
  3. 完善页面状态处理

    • 补充Cookie授权步骤,确保测试环境与手动操作一致:
      test.beforeEach(async({ page }) => {
        const landingPage = new LandingPage(page);
        await landingPage.goToLandingPage();
        await landingPage.cookiesButton.click(); // 新增接受Cookie操作
      });
      
    • 点击按钮前等待元素就绪,替换硬等待为自动等待:
      async login(user: string, password: string){
          await this.emailInputField.pressSequentially(user, {delay: 50});
          await this.contactDataCheckbox.click();
          await this.termsAndConditionsCheckbox.click();
          await expect(this.continueButton).toBeEnabled(); // 等待按钮可用
          await this.continueButton.click();
      }
      
  4. 同步会话状态

    • 导出手动操作时的localStorage数据,测试前注入页面:
      const localStorageData = require('./localStorageData.json'); // 本地存储的手动会话数据
      await page.addInitScript((data) => {
        Object.keys(data).forEach(key => localStorage.setItem(key, data[key]));
      }, localStorageData);
      

内容的提问来源于stack exchange,提问作者nZa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 19:02:31