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

GH自托管Runner中Playwright存储状态残留致测试失败问题咨询

问题:GitHub Actions自托管Runner中Playwright会话缓存导致重复登录失败

我正在测试一个采用Microsoft MFA强制登录的开发门户,用otpauth库处理MFA验证,同时通过Playwright的storage state功能实现一次登录复用会话,本地运行正常。但在组织的GitHub Actions自托管Runner环境中,首次测试正常,再次触发时Runner会缓存登录会话,导致测试因重复登录失败。

我有两个问题:

  1. 为何浏览器存储状态会在该Runner中被保留?
  2. 如何在会话已缓存时跳过登录步骤?

相关代码

playwright.config.ts

projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },

    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      timeout: 60000,
      dependencies: ['setup'],
    },

    {
      name: 'firefox',
      use: {
        ...devices['Desktop Firefox'],
        storageState: 'playwright/.auth/user.json',
      },
      timeout: 60000,
      dependencies: ['setup'],
    },
]

auth.setup.ts

import { test as setup, expect } from '@playwright/test';
import UtilLoginPage from '../pages/utilLoginPage';
import path from 'path';

const authFile = 'playwright/.auth/user.json'
let loginPage: UtilLoginPage

let getCredentials = async () => {
  const username = process.env.AUTOMATION_USER;
  const password = process.env.AUTOMATION_PASSWORD;

  if (!username || !password) {
    throw new Error('Environment variables AUTOMATION_USER and AUTOMATION_PASSWORD must be set');
  }
  return { username, password }
}

setup('authenticate', async ({ page }) => {
  
  // if (existsSync(authFile)) {
  //   console.log('Auth file already exists, skipping authentication setup');
  //   return;
  // }
  
  loginPage = new UtilLoginPage(page);
  await loginPage.open((await getCredentials()).username, (await getCredentials()).password);
  await page.context().storageState({ path: authFile });
});

补充说明:个人GH自托管Runner可手动删除playwright/.auth/user.json,无此问题,但组织的自托管Runner无法进行此操作。


解答

1. 浏览器存储状态保留的原因

  • GitHub自托管Runner默认会复用工作目录,不同于官方托管Runner每次运行都是全新环境,自托管Runner的文件系统会在多次任务间保留,所以playwright/.auth/user.json这个存储会话的文件不会自动消失。
  • 组织的自托管Runner大概率没有配置工作目录自动清理规则,加上你没有权限手动删除该文件,导致旧会话文件一直存在,每次运行都会被读取。

2. 跳过登录步骤的实现方案

方案一:在setup脚本中检查并复用有效会话

恢复你注释掉的文件检查逻辑,同时建议添加会话有效性验证,避免使用过期会话:

修改后的auth.setup.ts:

import { test as setup, expect } from '@playwright/test';
import UtilLoginPage from '../pages/utilLoginPage';
import { existsSync } from 'fs'; // 引入fs模块

const authFile = 'playwright/.auth/user.json'
let loginPage: UtilLoginPage

let getCredentials = async () => {
  const username = process.env.AUTOMATION_USER;
  const password = process.env.AUTOMATION_PASSWORD;

  if (!username || !password) {
    throw new Error('Environment variables AUTOMATION_USER and AUTOMATION_PASSWORD must be set');
  }
  return { username, password }
}

setup('authenticate', async ({ page }) => {
  // 检查auth文件是否存在
  if (existsSync(authFile)) {
    console.log('Auth file already exists, checking session validity...');
    // 尝试访问门户首页,验证是否已登录
    await page.goto('/');
    try {
      // 替换成你页面中标识登录状态的元素选择器,比如用户头像、用户名等
      await expect(page.locator('#user-profile-icon')).toBeVisible({ timeout: 5000 });
      console.log('Session is valid, skipping login');
      return;
    } catch (e) {
      console.log('Existing session is invalid, re-authenticating...');
    }
  }
  
  // 执行登录流程
  loginPage = new UtilLoginPage(page);
  await loginPage.open((await getCredentials()).username, (await getCredentials()).password);
  await page.context().storageState({ path: authFile });
});

方案二:在GitHub Actions workflow中强制清理auth目录

如果组织Runner允许执行shell命令,可以在测试前添加清理步骤,确保每次运行都是全新的会话:

jobs:
  test:
    runs-on: self-hosted
    steps:
      - uses: actions/checkout@v4
      # 清理auth目录,不存在则忽略错误
      - name: Clean up auth storage
        run: rm -rf playwright/.auth || true
      - name: Install dependencies
        run: npm install
      - name: Run Playwright tests
        run: npx playwright test

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 22:00:08