Playwright实现一次性认证时报Cookie expires字段值非法错误
报错信息
browser.newContext: Cookie should have a valid expires, only -1 or a positive number for the unix timestamp in seconds is allowed at C:\Users\v.shivarama.krishnan\source\repos\PlaywrightDemo\node_modules\@playwright\test\lib\index.js:595:23 at Object.context [as fn] (C:\Users\v.shivarama.krishnan\source\repos\PlaywrightDemo\node_modules\@playwright\test\lib\index.js:642:15)
根因
该报错为两个问题叠加导致的偶发故障:
- 存储状态导出时机错误:
global-setup.ts中完成登录点击后,没有等待页面跳转完成、所有认证Cookie完全写入浏览器上下文,就立刻执行storageState导出。此时部分Cookie还处于初始化阶段,expires字段可能是空值、0或者毫秒格式时间戳(Playwright要求该字段必须是-1或者秒级Unix时间戳正数),生成的admin.json包含非法Cookie数据。后续测试加载这个损坏文件创建浏览器上下文时,就会触发参数校验报错。 - API逻辑用反:测试用例中调用
context.storageState({path: 'xxx'})的作用是将当前上下文的会话状态导出到文件,不是从文件加载已有登录态。在每个测试用例的全新空上下文(默认无任何Cookie数据)里执行该导出操作,会直接覆盖global-setup提前生成的有效会话文件,大幅提升损坏文件出现的概率。
另外贴出的Spec.ts代码中第一个测试用例缺失test函数声明,属于基础语法错误,运行时也会抛出异常。
修复方案
- 调整
playwright.config.ts配置,在全局层级统一配置存储状态加载路径,不要在测试用例内手动处理会话加载/导出:
// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ globalSetup: require.resolve('./global-setup'), use: { // 所有测试默认加载global-setup生成的登录态 storageState: 'storage/admin.json', baseURL: 'https://abc.com' // 替换为实际业务站点地址 }, // 其余原有配置保持不变 })
- 修正
global-setup.ts逻辑,等待登录完全完成、所有Cookie落盘后再导出存储状态,同时替换不稳定的输入方法:
import { Browser, chromium, FullConfig } from '@playwright/test' async function globalSetup(config: FullConfig) { const browser = await chromium.launch({ headless: false }); await saveStorage(browser, 'storage/admin.json') await browser.close() } async function saveStorage(browser: Browser, saveStoragePath: string) { const page = await browser.newPage() await page.goto('/'); await page.waitForSelector("//input[@type='email']", { state: 'visible' }); await page.locator("//input[@type='email']").fill('ABC@com'); // 用fill替代type,输入稳定性更高 await page.locator("//input[@type='submit']").click(); await page.locator("//input[@type='password']").fill('(&^%'); await page.locator("//input[@type='submit']").click(); await page.locator('#idSIButton9').click(); // 关键修复:等待登录后业务页面的标识元素可见,确认登录流程完全走完 await page.waitForSelector('#ebiLink', { state: 'visible', timeout: 15000 }); // 加短等待兜底,确保跨域Cookie、过期时间字段全部写入完成 await page.waitForTimeout(1000); await page.context().storageState({ path: saveStoragePath }) } export default globalSetup
- 修正
Spec.ts测试用例代码,删除错误的storageState导出调用,补全缺失的测试用例声明:
import { test, expect } from "@playwright/test"; test.describe('Launch Browser', () => { // 补全第一个测试用例的声明 test('Verify interface search flow', async ({ page }) => { // 配置文件已自动加载登录态,无需手动处理会话 await page.goto('/'); await expect(page.locator('#ebiLink')).toBeVisible(); const texts = await page.locator('#ebiLink').textContent(); console.log("text of ebi is " + texts); await page.goto('/'); await expect(page.locator('text= Detailed Interfaces ')).toBeVisible(); await page.waitForSelector('#searchTab'); await page.waitForSelector('#InterfaceCard'); await page.locator('#searchTab').fill('VISHW-I7939'); await page.locator("button[type='button']").click(); await page.locator('#InterfaceCard').first().click(); await expect(page.locator('#ngb-nav-0')).toBeVisible(); const interfaceID = await page.locator("//span[@class='value-text']").first().allInnerTexts(); console.log('interface id is' + interfaceID); await page.waitForSelector('role=listbox'); await page.locator('role=listbox').click(); await page.locator('role=option').nth(1).click(); await expect(page.locator('#ngb-nav-0')).toBeVisible(); }); test('Move to shells screen', async ({ page }) => { await page.goto('/'); await expect(page.locator('#ListHeader')).toBeVisible(); const shells = await page.locator('#ListHeader').textContent(); console.log('Text of shells header is '+shells); }); });
- 每次执行测试需要重新生成登录态前,先删除
storage目录下旧的json文件,避免残留损坏文件被加载。
内容的提问来源于stack exchange,提问作者Vishwa nathan
相关产品推荐
相关产品推荐

