Refactor: Change how test runner handles pageWithUserData tests (#5922)

* refactor: change how test runner opens pageWithUserData instances

* fix: test move tabs

* fix: custom ca cert tests

* fix: update file patterns and improve error messages

* fix: improve electron app launch logic

* fix: update temporary directory handling for Electron app

* fix: ensure newline at end of file in index.ts

This change adds a newline at the end of the file to comply with coding standards.

* fix: improve error handling in recursiveCopy function

- Simplified error message when source path does not exist.
- Enhanced error handling to provide clearer guidance on usage of `page` fixture.

* fix(e2e): close collections after each tests

* fix: reuse the worker instance per file instead of per user data dir

* fix: revert ssl tests as serial run is fixed

* fix: change afterEach to afterAll for cleanup

fix: change afterEach to afterAll for cleanup

---------

Co-authored-by: Bijin Bruno <bijin@usebruno.com>
This commit is contained in:
Sid
2025-10-29 14:32:45 +05:30
committed by GitHub
parent 384aabf2af
commit 6e8cd55b76
17 changed files with 74 additions and 43 deletions

View File

@@ -26,6 +26,7 @@ module.exports = runESMImports().then(() => defineConfig([
files: [
'./eslint.config.js',
'tests/**/*.{ts,js}',
'playwright/**/*.{js,ts}',
'packages/bruno-app/**/*.{js,jsx,ts}',
'packages/bruno-app/src/test-utils/mocks/codemirror.js',
'packages/bruno-cli/**/*.js',

View File

@@ -5,6 +5,26 @@ import * as fs from 'fs';
const electronAppPath = path.join(__dirname, '../packages/bruno-electron');
const existsAsync = (filepath: string) => fs.promises.access(filepath).then(() => true).catch(() => false);
async function recursiveCopy(src: string, dest: string) {
if (!await existsAsync(src)) {
throw new Error(`${src} doesn't exist`);
}
const files = await fs.promises.readdir(src, {
recursive: true,
withFileTypes: true
});
for (const file of files) {
if (!file.isFile()) continue;
const fullPath = path.join(src, file.name);
const fullDestPath = path.join(dest, file.name);
await fs.promises.copyFile(fullPath, fullDestPath);
}
}
export const test = baseTest.extend<
{
context: BrowserContext;
@@ -17,7 +37,7 @@ export const test = baseTest.extend<
createTmpDir: (tag?: string) => Promise<string>;
launchElectronApp: (options?: { initUserDataPath?: string; userDataPath?: string; dotEnv?: Record<string, string> }) => Promise<ElectronApplication>;
electronApp: ElectronApplication;
reuseOrLaunchElectronApp: (options?: { initUserDataPath?: string; userDataPath?: string; dotEnv?: Record<string, string> }) => Promise<ElectronApplication>;
reuseOrLaunchElectronApp: (options?: { initUserDataPath?: string; testFile?: string; userDataPath?: string; dotEnv?: Record<string, string> }) => Promise<ElectronApplication>;
}
>({
createTmpDir: [
@@ -150,8 +170,8 @@ export const test = baseTest.extend<
reuseOrLaunchElectronApp: [
async ({ launchElectronApp }, use, testInfo) => {
const apps: Record<string, ElectronApplication> = {};
await use(async ({ initUserDataPath, userDataPath, dotEnv = {} } = {}) => {
const key = userDataPath || initUserDataPath;
await use(async ({ initUserDataPath, testFile, userDataPath, dotEnv = {} } = {}) => {
const key = testFile || userDataPath || initUserDataPath;
if (key && apps[key]) {
return apps[key];
}
@@ -191,13 +211,21 @@ export const test = baseTest.extend<
}
},
pageWithUserData: async ({ reuseOrLaunchElectronApp }, use, testInfo) => {
pageWithUserData: async ({ reuseOrLaunchElectronApp, createTmpDir }, use, testInfo) => {
const testDir = path.dirname(testInfo.file);
const initUserDataPath = path.join(testDir, 'init-user-data');
const app = await reuseOrLaunchElectronApp(
(await fs.promises.stat(initUserDataPath).catch(() => false)) ? { initUserDataPath } : {}
);
const tmpAppDataDir = await createTmpDir();
try {
await recursiveCopy(initUserDataPath, tmpAppDataDir);
} catch (err) {
if (err instanceof Error && err.message.includes('doesn\'t exist')) {
throw new Error(`${initUserDataPath} doesn't exist, either add one or if you don't need an initial state then use the \`page\` fixture instead of \`pageWithUserData\`.`);
}
throw err;
}
const app = await reuseOrLaunchElectronApp({ initUserDataPath: tmpAppDataDir, testFile: testInfo.file });
const context = await app.context();
const page = await app.firstWindow();

View File

@@ -2,12 +2,12 @@ import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Cross-Collection Drag and Drop for folder', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify cross-collection folder drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
test('Verify cross-collection folder drag and drop', async ({ page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
@@ -121,7 +121,7 @@ test.describe('Cross-Collection Drag and Drop for folder', () => {
});
test('Verify cross-collection folder drag and drop, a duplicate folder exist. expected to throw error toast', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
// Create first collection (source) - use unique names for this test

View File

@@ -2,12 +2,12 @@ import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Cross-Collection Drag and Drop', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify request drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
test('Verify request drag and drop', async ({ page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
@@ -79,7 +79,7 @@ test.describe('Cross-Collection Drag and Drop', () => {
});
test('Expected to show error toast message, when duplicate request found in drop location', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
// Create first collection (source-collection)

View File

@@ -2,12 +2,12 @@ import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Tag persistence', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify tag persistence while moving requests within a collection', async ({ pageWithUserData: page, createTmpDir }) => {
test('Verify tag persistence while moving requests within a collection', async ({ page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('test-collection');
@@ -72,7 +72,7 @@ test.describe('Tag persistence', () => {
await expect(page.getByRole('button', { name: 'smoke' })).toBeVisible();
});
test('verify tag persistence while moving requests between folders', async ({ pageWithUserData: page, createTmpDir }) => {
test('verify tag persistence while moving requests between folders', async ({ page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.getByLabel('Create Collection').click();
await page.getByLabel('Name').fill('test-collection');

View File

@@ -1,22 +1,28 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Move tabs', () => {
test('Verify tab move by drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
test.afterEach(async ({ page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify tab move by drag and drop', async ({ page, createTmpDir }) => {
// Create a collection
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByLabel('Name').fill('source-collection-drag-drop');
await page.getByLabel('Location').fill(await createTmpDir('source-collection-drag-drop'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection-drag-drop' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection-drag-drop' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a folder in the collection
const sourceCollection = page.locator('.collection-name').filter({ hasText: 'source-collection' });
const sourceCollection = page.locator('.collection-name').filter({ hasText: 'source-collection-drag-drop' });
await sourceCollection.locator('.collection-actions').hover();
await sourceCollection.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
@@ -89,22 +95,22 @@ test.describe('Move tabs', () => {
}
});
test('Verify tab move by keyboard shortcut', async ({ pageWithUserData: page, createTmpDir }) => {
test('Verify tab move by keyboard shortcut', async ({ page, createTmpDir }) => {
// Create a collection
await page.locator('.dropdown-icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
await page.getByLabel('Name').fill('source-collection');
await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
await page.getByLabel('Name').fill('source-collection-keyboard-shortcut');
await page.getByLabel('Location').fill(await createTmpDir('source-collection-keyboard-shortcut'));
await page.getByRole('button', { name: 'Create', exact: true }).click();
// Wait for collection to appear and click on it
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection-keyboard-shortcut' })).toBeVisible();
await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection-keyboard-shortcut' }).click();
await page.getByLabel('Safe Mode').check();
await page.getByRole('button', { name: 'Save' }).click();
// Create a folder in the collection
const sourceCollection = page.locator('.collection-name').filter({ hasText: 'source-collection' });
const sourceCollection = page.locator('.collection-name').filter({ hasText: 'source-collection-keyboard-shortcut' });
await sourceCollection.locator('.collection-actions').hover();
await sourceCollection.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();

View File

@@ -3,7 +3,7 @@ import path from 'path';
test.describe('Collection Environment Create Tests', () => {
test('should import collection and create environment for request usage', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');

View File

@@ -3,7 +3,7 @@ import path from 'path';
test.describe('Global Environment Create Tests', () => {
test('should import collection and create global environment for request usage', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
const openApiFile = path.join(__dirname, 'fixtures', 'bruno-collection.json');

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../../playwright';
import path from 'path';
test.describe('Collection Environment Import Tests', () => {
test('should import collection environment from file', async ({ pageWithUserData: page, createTmpDir }) => {
test('should import collection environment from file', async ({ page, createTmpDir }) => {
const openApiFile = path.join(__dirname, 'fixtures', 'collection.json');
const envFile = path.join(__dirname, 'fixtures', 'collection-env.json');

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../../playwright';
import path from 'path';
test.describe('Global Environment Import Tests', () => {
test('should import global environment from file', async ({ pageWithUserData: page, createTmpDir }) => {
test('should import global environment from file', async ({ page, createTmpDir }) => {
const openApiFile = path.join(__dirname, 'fixtures', 'collection.json');
const globalEnvFile = path.join(__dirname, 'fixtures', 'global-env.json');

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../playwright';
import { openCollectionAndAcceptSandbox, closeAllCollections } from '../utils/page';
test.describe('Global Environment Variables - Non-string Values', () => {
test.afterAll(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ pageWithUserData: page }) => {
// Cleanup: close all collections
await closeAllCollections(page);
});

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../../playwright';
import { closeAllCollections, openCollectionAndAcceptSandbox } from '../../utils/page';
test.describe.serial('Dynamic Variable Interpolation', () => {
test.afterAll(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../playwright';
import { closeAllCollections } from '../utils/page';
test.describe('manage protofile', () => {
test.afterAll(async ({ page }) => {
test.afterAll(async ({ pageWithUserData: page }) => {
await closeAllCollections(page);
});

View File

@@ -2,7 +2,7 @@ import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Code Generation URL Encoding', () => {
test.afterEach(async ({ pageWithUserData: page }) => {
test.afterEach(async ({ page }) => {
try {
const modalCloseButton = page.locator('[data-test-id="modal-close-button"]');
if (await modalCloseButton.isVisible()) {
@@ -15,7 +15,7 @@ test.describe('Code Generation URL Encoding', () => {
});
test('Should generate code with proper URL encoding for unencoded input', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
await page.locator('.dropdown-icon').click();
@@ -57,7 +57,7 @@ test.describe('Code Generation URL Encoding', () => {
});
test('Should generate code with proper URL encoding for encoded input', async ({
pageWithUserData: page,
page,
createTmpDir
}) => {
await page.locator('.dropdown-icon').click();

View File

@@ -30,7 +30,7 @@ test.describe.serial('basic ssl success', () => {
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();

View File

@@ -2,7 +2,6 @@ import { test, expect } from '../../../../../playwright';
test.describe.serial('self signed rejected', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('self-signed-badssl').click();
await page.getByLabel('Developer Mode(use only if').check();
@@ -30,7 +29,6 @@ test.describe.serial('self signed rejected', () => {
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();

View File

@@ -2,7 +2,6 @@ import { test, expect } from '../../../../../playwright';
test.describe.serial('custom invalid ca cert added to the config and keep default ca certs', () => {
test('developer mode', async ({ pageWithUserData: page }) => {
// init dev mode
await page.getByText('custom-ca-certs').click();
await page.getByLabel('Developer Mode(use only if').check();
@@ -29,7 +28,6 @@ test.describe.serial('custom invalid ca cert added to the config and keep defaul
});
test('safe mode', async ({ pageWithUserData: page }) => {
// init safe mode
await page.getByText('Developer Mode').click();
await page.getByLabel('Safe Mode').check();