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

Playwright成功获取Token但截图无法显示认证视图问题排查

JWT认证React应用的Playwright测试登录后截图异常排查

问题描述

为采用JWT认证的React应用编写Playwright测试时,登录流程已成功获取access和refresh token,也能执行截图操作,但测试并未实际完成用户登录,截图无法显示认证后的视图,怀疑问题出在CurrentUserContext的使用上。

问题代码

测试文件frontend/playwright/signed-in-landing-page.spec.js中末尾的await page.screenshot({...});未能得到预期结果,完整代码如下:

import { test, expect } from '@playwright/test';

const BASE_URL = 'http://localhost:8000';

test.describe('Signed-in Landing Page', () => {
  test('desktop', async ({ page }) => {
    // Navigates to sign-in page
    await page.goto(`${BASE_URL}/signin`);

    // Fills in credentials and submit
    await page.fill('input[name="username"]', 'user');
    await page.fill('input[name="password"]', 'qwerqwer*');
    await page.click('button[type="submit"]');

    // Waits for navigation and network idle
    await page.waitForLoadState('networkidle');

    // Logs localStorage contents
    const localStorageData = await page.evaluate(() => {
      const data = {};
      for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        data[key] = localStorage.getItem(key);
      }
      return data;
    });
    console.log('localStorage contents:', localStorageData);

    // Verifies user is signed in
    const accessToken = await page.evaluate(() => localStorage.getItem('accessToken'));
    console.log('Access token:', accessToken);

    if (accessToken) {
      // Check for an authenticated element
      const signOutElement = await page.locator('text=Sign out').count();
      console.log('Sign out element count:', signOutElement);
      
      if (signOutElement === 0) {
        console.error('Sign out element not found');
        const pageContent = await page.content();
        console.log('Page content:', pageContent);
      } else {
        await expect(page.locator('text=Sign out')).toBeVisible();
      }
    }

    // Take a screenshot. FAIL. The screenshot does not show Auth view as expected.
    await page.screenshot({
      path: `screenshots/signed-in-landing-page-desktop.png`,
      fullPage: false
    });
  });
});

已确认正常的基础测试

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

test('has title', async ({ page }) => {
  await page.goto('http://localhost:8080');
  await expect(page).toHaveTitle(/Odyssey/);

  await page.waitForLoadState('networkidle');

  // Takes a screenshot, returns the website in logged out view as expected
  await page.screenshot({
    path: `screenshots/setup-confirmed.png`,
    fullPage: false
  });
});

Playwright配置文件

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

module.exports = defineConfig({
  testDir: './playwright',
  fullyParallel: true,
  forbidOnly: !!process.env.CI, 
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined, 
  reporter: [['list'], ['json', { outputFile: 'test-results.json' }]],
  outputDir: 'screenshots',
  use: {
    baseURL: 'http://localhost:8080',
    trace: 'on-first-retry',
    screenshot: 'on',
    headless: true,
  },
  projects: [
    {
      name: 'chromium',
      use: { 
        ...devices['Desktop Chrome'],
      },
    },
  ],
  webServer: {
    command: 'python3 manage.py runserver',
    url: 'http://localhost:8080',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000
  },
});

预期与实际行为

  • 预期行为:成功获取Token后,截图显示登录后的视图
  • 实际行为:测试成功获取Token并截图,但截图未显示登录后的视图

疑问

  1. 认证流程中是否遗漏关键步骤导致用户未完全登录?
  2. Token的存储或使用是否存在问题导致登录未完成?

环境与使用步骤

理想环境为Gitpod IDE:

  • 终端1:运行python3 manage.py runserver启动后端
  • 终端2:进入frontend目录,执行npm start启动前端
  • 终端3:进入frontend目录,执行npx install playwright --with-deps和xvfb-run npx playwright test [test-name here]运行测试

相关版本:Playwright 1.47.0、React 17.0.2、Node.js 16.0、Windows系统


排查思路与解决方案

1. 统一BASE_URL配置

测试文件中BASE_URL为http://localhost:8000,但Playwright配置和基础测试使用http://localhost:8080,端口不匹配会导致登录请求发送到错误地址,前端无法触发登录状态更新。修改测试文件中的BASE_URL为前端实际运行端口:

const BASE_URL = 'http://localhost:8080';

2. 等待登录后的路由跳转或关键元素

React单页应用登录后通常是路由跳转而非页面重载,仅用networkidle无法确保组件渲染完成。替换原等待逻辑,改为等待目标路由或认证元素:

// 替换原waitForLoadState
await page.waitForURL(`${BASE_URL}/dashboard`); // 替换为实际登录后跳转的路由
await expect(page.locator('text=Sign out')).toBeVisible({ timeout: 5000 });

3. 验证CurrentUserContext状态同步

React应用中登录后需等待Context状态更新到组件,可通过等待登录后独有的元素(如用户名、用户头像)确认状态已同步:

await expect(page.locator('[data-testid="user-avatar"]')).toBeVisible();

4. 检查Token有效性

获取到Token不代表其有效,可在测试中解码JWT验证过期时间和用户信息:

const accessToken = await page.evaluate(() => localStorage.getItem('accessToken'));
if (accessToken) {
  const payload = JSON.parse(atob(accessToken.split('.')[1]));
  expect(payload.exp * 1000).toBeGreaterThan(Date.now()); // 验证token未过期
}

5. 开启可视化调试

将Playwright配置中的headless: true改为false,并添加慢动作执行,直观观察测试过程:

use: {
  // ...其他配置
  headless: false,
  slowMo: 500,
}

6. 监听登录请求响应

确认后端返回的登录响应是否正常,避免因接口错误导致前端未更新状态:

// 登录前监听登录接口
const loginRequest = page.waitForRequest(req => req.url().includes('/api/auth/login') && req.method() === 'POST');
await page.click('button[type="submit"]');
const response = await loginRequest;
expect(response.status()).toBe(200);
console.log('Login response:', await response.json());

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 03:15:56