Playwright 使用 Firebase 进行“重用状态”和“重用身份验证”

Ben*_*rri 6 testing automation firebase typescript playwright

我试图确保在每个运行的测试中,我不必登录到我正在测试的平台。为了实现这一目标,我正在实施“重用状态”和“重用身份验证”。但是,Firebase 使用 indexedDB 而不是 Cookie 或 SessionStorage。StorageState 此处不持久保存 indexedDB。也许你知道这个问题的解决方案?

全局设置.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 });

  //save state
  await page.context().storageState({ path: "./LoginAuth.json" });

  await browser.close();
}

export default globalSetup;
Run Code Online (Sandbox Code Playgroud)

有用的链接:

https://playwright.dev/docs/auth#reuse-authentication-in-playwright-test https://github.com/microsoft/playwright/issues/11164

pag*_*num 3

也可以将indexedDB存储在storageState中,然后稍后恢复它,尽管你必须跳过一些步骤才能做到这一点。

我所做的是将indexedDB存储在我的localStorage中,这样当您保存storageState时,您就拥有了它的所有内容。它的工作原理是将数据库名称保存为键,将数据库存储对象的字符串化 JSON 保存为值,因此 localStorage 中的每个条目都是一个数据库。我在剧作家的身份验证文档中使用基本身份验证,因此在设置文件中使用以下代码:

import { test as setup } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

const URL_LOCAL = "http://localhost:8100"

setup('authenticate', async ({ page }) => {
    await page.goto(URL_LOCAL);

    // Do the login routine here...

    await page.evaluate(async () => {
        window.localStorage.clear();
        window.sessionStorage.clear();

        const indexedDB = window.indexedDB;
        const dbs = await indexedDB.databases();

        for (let dbIndex = 0; dbIndex < dbs.length; dbIndex++) {
            const dbInfo = dbs[dbIndex];
            const db: IDBDatabase = await new Promise((resolve, reject) => {
                let req = indexedDB.open(dbInfo.name as string, dbInfo.version);
                req.onsuccess = (event: any) => {
                    resolve(event.target.result);
                }
                req.onupgradeneeded = (event: any) => {
                    resolve(event.target.result);
                }
                req.onerror = (e) => {
                    reject(e);
                }
            });

            let dbRes: { [k: string]: any } = {};

            for (let objectStorageIndex = 0; objectStorageIndex < db.objectStoreNames.length; objectStorageIndex++) {
                const objectStorageName = db.objectStoreNames[objectStorageIndex];
                let objectStorageRes: { [k: string]: any } = {};

                // Open a transaction to access the firebaseLocalStorage object store
                const transaction = db.transaction([objectStorageName], 'readonly');
                const objectStore = transaction.objectStore(objectStorageName);

                // Get all keys and values from the object store
                const getAllKeysRequest = objectStore.getAllKeys();
                const getAllValuesRequest = objectStore.getAll();

                const keys: any = await new Promise((resolve, reject) => {
                    getAllKeysRequest.onsuccess = (event: any) => {
                        resolve(event.target.result);
                    }
                    getAllKeysRequest.onerror = (e) => {
                        reject(e);
                    }
                });

                const values: any = await new Promise((resolve, reject) => {
                    getAllValuesRequest.onsuccess = (event: any) => {
                        resolve(event.target.result);
                    }
                    getAllValuesRequest.onerror = (e) => {
                        reject(e);
                    }
                });

                for (let i = 0; i < keys.length; i++) {
                    objectStorageRes[keys[i]] = JSON.stringify(values[i]);
                }

                dbRes[objectStorageName] = objectStorageRes;
            }
            localStorage.setItem(db.name, JSON.stringify(dbRes));
        }
    });

    await page.context().storageState({ path: authFile });
});
Run Code Online (Sandbox Code Playgroud)

然后我们需要一个额外的文件auth.utils.ts来重建indexedDB数据库,使用以下代码

import { Page } from "@playwright/test";
import { readFileSync } from 'fs';

const URL_LOCAL = "http://localhost:8100"

export const authenticate = async (page: Page) => {
    await page.goto(URL_LOCAL);

    const auth = JSON.parse(readFileSync('playwright/.auth/user.json', 'utf-8'));

    await page.evaluate(async (auth) => {
        const indexedDB = window.indexedDB;
        const localStorageAuth: any[] = auth.origins[0].localStorage

        for (const storage of localStorageAuth) {
            const dbName = storage.name;
            const dbData = JSON.parse(storage.value as string);
            const tables = Object.keys(dbData);

            const db: IDBDatabase = await new Promise((resolve, reject) => {
                let req = indexedDB.open(dbName as string);
                req.onsuccess = (event: any) => {
                    resolve(event.target.result);
                };
                req.onupgradeneeded = (event: any) => {
                    resolve(event.target.result);
                };
                req.onerror = (e) => {
                    reject(e);
                };
                req.onblocked = (event: any) => {
                    reject(event);
                };
            });

            for (const table of tables) {
                const transaction = db.transaction([table], 'readwrite');
                const objectStore = transaction.objectStore(table);

                for (const key of Object.keys(dbData[table])) {
                    const value = dbData[table][key];

                    // Parse value in case of keyPath
                    let parsedValue = typeof value !== "string" ? JSON.stringify(value) : value;
                    try {
                        parsedValue = JSON.parse(parsedValue);
                    } catch (e) {
                        // value type is not json, nothing to do
                    }

                    if (objectStore.keyPath != null) {
                        objectStore.put(parsedValue);
                    } else {
                        objectStore.put(parsedValue, key);
                    }
                }
            }

        }
    }, auth);
}
Run Code Online (Sandbox Code Playgroud)

最后,将其添加到您希望对其进行身份验证的每个测试中:

import { test, expect } from '@playwright/test';
import { authenticate } from './auth.utils';

/// ...

test.beforeEach(async ({ page }) => {
  await authenticate(page);
});

test('example test', async ({ page }) => {
    /// Test content
});

/// More tests
Run Code Online (Sandbox Code Playgroud)

我在该主题上找到的大多数答案都非常特定于 Firebase DB,通常 Firebase DB 名称是硬编码的,这不是很灵活。我有多个数据库,因此此代码可以处理多个不同的数据库。

希望这可以帮助!