refactor(infra): record legacy data to improve testing stability (#4590)

This commit is contained in:
Joooye_34
2023-10-13 11:03:42 +08:00
committed by GitHub
parent 286347420d
commit 6ea10860b4
42 changed files with 2763 additions and 140 deletions

View File

@@ -0,0 +1,18 @@
import { dirname, join } from 'node:path';
import type { Page } from '@playwright/test';
declare global {
function readAffineDatabase(): Promise<any>;
function writeAffineDatabase(data: any, binaries: any): Promise<void>;
function readAffineLocalStorage(): Promise<any>;
function writeAffineLocalStorage(data: any): Promise<void>;
}
export async function patchDataEnhancement(page: Page) {
const idbPath = join(dirname(require.resolve('idb')), 'umd.js');
await page.addInitScript({ path: idbPath });
const patchPath = join(__dirname, './storage-patch.js');
await page.addInitScript({ path: patchPath });
}

View File

@@ -0,0 +1,109 @@
import { readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { mkdirp, readJSON } from 'fs-extra';
interface SnapshotData {
idbData: Record<string, any>;
localStorageData: Record<string, string>;
binaries: Record<string, number[]>;
}
interface BinaryIndexData {
name: string;
start: number;
end: number;
}
export class SnapshotStorage {
idbFilePath: string;
localStorageFilePath: string;
binaryIndexPath: string;
binaryFilePath: string;
constructor(version: string) {
// The snapshots data is stored in "@affine-test/fixtures/legacy", just keep all fixtures in one place
const legacyReadMeFilePath = require.resolve(
'@affine-test/fixtures/legacy/README.md'
);
const dir = dirname(legacyReadMeFilePath);
this.idbFilePath = join(dir, version, 'idb.json');
this.binaryFilePath = join(dir, version, 'idb.bin');
this.binaryIndexPath = join(dir, version, 'idb_index.json');
this.localStorageFilePath = join(dir, version, 'local-storage.json');
}
async read(): Promise<SnapshotData> {
const {
idbFilePath,
localStorageFilePath,
binaryIndexPath,
binaryFilePath,
} = this;
const [idbData, localStorageData, binaryIndexArr, binaryContent] =
await Promise.all([
readJSON(idbFilePath),
readJSON(localStorageFilePath),
readJSON(binaryIndexPath) as Promise<BinaryIndexData[]>,
readFile(binaryFilePath),
]);
const binaries: Record<string, number[]> = {};
for (const index of binaryIndexArr) {
const chunk = binaryContent.subarray(index.start, index.end);
binaries[index.name] = Array.from(chunk);
}
return {
binaries,
idbData,
localStorageData,
};
}
async write(data: SnapshotData) {
const {
idbFilePath,
localStorageFilePath,
binaryIndexPath,
binaryFilePath,
} = this;
const { idbData, localStorageData, binaries } = data;
await mkdirp(dirname(idbFilePath));
const binaryIndexData: BinaryIndexData[] = [];
const binaryBuffers: Buffer[] = [];
let currentIndex = 0;
for (const [name, value] of Object.entries(binaries)) {
const buffer = Buffer.from(value);
const endIndex = currentIndex + buffer.length;
binaryIndexData.push({
name,
start: currentIndex,
end: endIndex,
});
binaryBuffers.push(buffer);
currentIndex += buffer.length;
}
await Promise.all([
writeFile(
localStorageFilePath,
JSON.stringify(localStorageData, null, 2),
'utf-8'
),
writeFile(idbFilePath, JSON.stringify(idbData, null, 2), 'utf-8'),
writeFile(
binaryIndexPath,
JSON.stringify(binaryIndexData, null, 2),
'utf-8'
),
writeFile(binaryFilePath, Buffer.concat(binaryBuffers), 'utf-8'),
]);
}
}

View File

@@ -0,0 +1,141 @@
/**
* @type {import('idb')}
*/
const idb = window.idb;
const createUniqueIndex = (() => {
let index = 0;
return () => ++index;
})();
function replaceBinary(value, binaries) {
if (value instanceof Uint8Array) {
const name = `__BINARY__${createUniqueIndex()}`;
binaries[name] = Array.from(value);
return name;
}
if (Array.isArray(value)) {
return value.map(item => replaceBinary(item, binaries));
}
if (typeof value === 'object' && value !== null) {
const replaced = {};
for (const key of Object.keys(value)) {
replaced[key] = replaceBinary(value[key], binaries);
}
return replaced;
}
return value;
}
function recoveryBinary(value, binaries) {
if (typeof value === 'string') {
const arr = binaries[value];
if (arr) {
return new Uint8Array(arr);
}
}
if (Array.isArray(value)) {
return value.map(item => recoveryBinary(item, binaries));
}
if (typeof value === 'object' && value !== null) {
const result = {};
for (const key of Object.keys(value)) {
result[key] = recoveryBinary(value[key], binaries);
}
return result;
}
return value;
}
async function readAffineDatabase() {
const idbData = [];
const binaries = {};
const databases = await indexedDB.databases();
for (const databaseInfo of databases) {
const idbDatabase = await idb.openDB(
databaseInfo.name,
databaseInfo.version
);
if (!idbDatabase) {
throw new Error('idbDatabase is null');
}
const stores = [];
const objectStoreNames = Array.from(idbDatabase.objectStoreNames);
const transaction = idbDatabase.transaction(objectStoreNames, 'readonly');
for (const storeName of objectStoreNames) {
const objectStore = transaction.objectStore(storeName);
const objectValues = await objectStore.getAll();
stores.push({
name: storeName,
keyPath: objectStore.keyPath,
values: replaceBinary(objectValues, binaries),
});
}
idbData.push({ ...databaseInfo, stores });
}
return { idbData, binaries };
}
async function writeAffineDatabase(allDatabases, binaries) {
for (const database of allDatabases) {
const idbDatabase = await idb.openDB(database.name, database.version, {
upgrade(db) {
for (const objectStore of database.stores) {
db.createObjectStore(objectStore.name, {
keyPath: objectStore.keyPath,
});
}
},
});
for (const store of database.stores) {
const transaction = idbDatabase.transaction(store.name, 'readwrite');
const objectStore = transaction.objectStore(store.name);
for (const value of store.values) {
await objectStore.add(recoveryBinary(value, binaries));
}
}
}
}
async function readAffineLocalStorage() {
const data = {};
const keys = [
'jotai-workspaces',
'last_page_id',
'last_workspace_id',
'affine-local-workspace',
'is-first-open',
];
for (const key of keys) {
const value = window.localStorage.getItem(key);
data[key] = value;
}
return data;
}
async function writeAffineLocalStorage(data) {
for (const [key, value] of Object.entries(data)) {
window.localStorage.setItem(key, value);
}
}
window.readAffineDatabase = readAffineDatabase;
window.writeAffineDatabase = writeAffineDatabase;
window.readAffineLocalStorage = readAffineLocalStorage;
window.writeAffineLocalStorage = writeAffineLocalStorage;

View File

@@ -6,7 +6,8 @@
"exports": {
"./electron": "./electron.ts",
"./playwright": "./playwright.ts",
"./utils/*": "./utils/*.ts"
"./utils/*": "./utils/*.ts",
"./e2e-enhance/*": "./e2e-enhance/*.ts"
},
"devDependencies": {
"@node-rs/argon2": "^1.5.2",

View File

@@ -5,5 +5,5 @@
"noEmit": false,
"outDir": "lib"
},
"include": ["./*.ts", "utils"]
"include": ["./*.ts", "utils", "e2e-enhance"]
}