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

Playwright复用Firebase认证状态:IndexedDB持久化方案咨询

问题

我希望每次运行Playwright测试时无需重复登录被测平台,因此尝试实现「复用状态」与「复用认证」功能。但被测平台基于Firebase构建,Firebase使用IndexedDB存储认证数据,而Playwright的storageState方法无法持久化IndexedDB数据,导致复用状态后仍需重新登录,求解决方案。

现有global-setup.ts代码

import { Browser, chromium, expect, Page } from "@playwright/test";

async function globalSetup(){
  const browser = await chromium.launch({headless: false});
  const context = await browser.newContext();
  const page: Page = await context.newPage();
  await page.goto("<'url'>")
  await page.getByLabel('Email').fill("<'email'>");
  await page.getByLabel('Password').fill("<'password'>");
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect (page.getByRole('link', { name: 'logo' })).toBeVisible({ timeout: 30000 });

  // 保存状态
  await page.context().storageState({ path: "./LoginAuth.json" });

  await browser.close();
}

export default globalSetup;

解决方案

方法1:使用持久化浏览器上下文

Playwright的持久化上下文会完整保存包括IndexedDB在内的所有浏览器存储数据,是适配Firebase认证场景的最简方案。

修改global-setup.ts为创建持久化上下文:

import { chromium } from "@playwright/test";
import * as path from "path";

async function globalSetup() {
  // 指定持久化存储目录
  const userDataDir = path.join(__dirname, ".playwright-user-data");
  const browser = await chromium.launchPersistentContext(userDataDir, {
    headless: false,
  });

  const page = await browser.newPage();
  await page.goto("<'url'>");
  await page.getByLabel('Email').fill("<'email'>");
  await page.getByLabel('Password').fill("<'password'>");
  await page.getByRole('button', { name: 'Sign in' }).click();
  // 确保登录完成
  await page.waitForSelector('role=link[name="logo"]', { timeout: 30000 });

  await browser.close();
}

export default globalSetup;

在playwright.config.ts中配置测试用例复用该上下文:

import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    // 复用持久化用户数据目录
    userDataDir: "./.playwright-user-data",
  },
});

方法2:手动导出/导入IndexedDB数据

若不想使用持久化上下文,可通过evaluate方法手动读写IndexedDB数据,实现状态复用。

步骤1:在全局初始化中导出IndexedDB数据

修改global-setup.ts,添加导出逻辑:

import { Browser, chromium, expect, Page } from "@playwright/test";
import * as fs from "fs";

async function globalSetup(){
  const browser = await chromium.launch({headless: false});
  const context = await browser.newContext();
  const page: Page = await context.newPage();
  await page.goto("<'url'>")
  await page.getByLabel('Email').fill("<'email'>");
  await page.getByLabel('Password').fill("<'password'>");
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByRole('link', { name: 'logo' })).toBeVisible({ timeout: 30000 });

  // 保存Cookie、SessionStorage等状态
  await page.context().storageState({ path: "./LoginAuth.json" });

  // 导出Firebase的IndexedDB数据(通常库名为firebaseLocalStorageDb)
  const indexedDbData = await page.evaluate(async () => {
    const dbName = "firebaseLocalStorageDb";
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(dbName);
      request.onsuccess = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        const objStoreNames = db.objectStoreNames;
        const data: Record<string, any[]> = {};

        const transactions = Array.from(objStoreNames).map((storeName) => {
          return new Promise((res) => {
            const transaction = db.transaction(storeName, "readonly");
            const store = transaction.objectStore(storeName);
            const getAllRequest = store.getAll();
            getAllRequest.onsuccess = () => {
              data[storeName] = getAllRequest.result;
              res(null);
            };
          });
        });

        Promise.all(transactions).then(() => {
          db.close();
          resolve({ dbName, data });
        });
      };
      request.onerror = () => reject(request.error);
    });
  });

  // 保存IndexedDB数据到文件
  fs.writeFileSync("./IndexedDbAuth.json", JSON.stringify(indexedDbData));

  await browser.close();
}

export default globalSetup;

步骤2:在测试前导入IndexedDB数据

在测试文件的beforeAll中添加导入逻辑:

import { test } from "@playwright/test";
import * as fs from "fs";

test.beforeAll(async ({ browser }) => {
  const storageState = JSON.parse(fs.readFileSync("./LoginAuth.json", "utf-8"));
  const indexedDbData = JSON.parse(fs.readFileSync("./IndexedDbAuth.json", "utf-8"));

  const context = await browser.newContext({ storageState });
  const page = await context.newPage();
  await page.goto("<'url'>"); // 先打开站点初始化IndexedDB环境

  // 导入IndexedDB数据
  await page.evaluate((dbData) => {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(dbData.dbName);
      request.onsuccess = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        const transactions = Object.entries(dbData.data).map(([storeName, items]) => {
          return new Promise((res) => {
            const transaction = db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            store.clear(); // 清空现有数据(可选)
            items.forEach(item => store.add(item));
            transaction.oncomplete = () => res(null);
          });
        });

        Promise.all(transactions).then(() => {
          db.close();
          resolve(null);
        });
      };
      request.onerror = () => reject(request.error);
    });
  }, indexedDbData);

  await context.close();
});

方法3:使用Firebase自定义认证令牌

若有权限访问Firebase Admin SDK,可直接生成自定义令牌实现无UI登录,效率更高且不依赖浏览器存储:

import { test } from "@playwright/test";
import * as admin from "firebase-admin";

test.beforeAll(async () => {
  // 初始化Firebase Admin SDK(需提前配置服务账号密钥)
  admin.initializeApp({
    credential: admin.credential.cert("./service-account-key.json"),
  });

  // 生成自定义认证令牌
  const uid = "目标用户UID";
  const customToken = await admin.auth().createCustomToken(uid);

  // 在测试上下文注入令牌完成登录
  test.use({
    contextOptions: {
      async beforePage(page) {
        await page.addInitScript((token) => {
          window.firebase.auth().signInWithCustomToken(token);
        }, customToken);
      },
    },
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 02:57:09