mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-09 06:55:03 +00:00
test: workspace import and validation testcase (TC-969) (#8349)
* test cases for workspace import and validation TC-969, jira: https://usebruno.atlassian.net/browse/BRU-3575 * incorporated comments, moved findWorkspaceDirByName function to helpers.ts, fixed all the comments * modified as per comment provided , removed css locators , used playwright inbuilt methods , handled timeout * Created file structure as per comment provided, added tc-id , resolved code-rabbit review * incorporated comments removed commented line, removed timeouts, modified package.json added package in dev dependencies * changed const l to locators for better readbility * - Reorganized test helpers: split title-bar locators into title-bar.ts and import-workspace flow into workspace/import-workspace.ts for reuse - Replaced brittle .bruno-modal-card/CSS locators with stable role/testid/label based locators - Added a data-testid for the Import Workspace modal and removed the redundant one - Cleaned up unnecessary comments - Updated package-lock.json * minor changes * minor changes * addressed comments * addressed comments for variable naming * minor changes
This commit is contained in:
15
tests/utils/page/title-bar.ts
Normal file
15
tests/utils/page/title-bar.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Page, test } from '../../../playwright';
|
||||
|
||||
export const buildTitleBarLocators = (page: Page) => ({
|
||||
workspaceMenuTrigger: () => page.getByTestId('workspace-menu'),
|
||||
activeWorkspaceName: () => page.getByTestId('workspace-name'),
|
||||
importWorkspaceOption: () => page.getByTestId('workspace-menu-import-workspace')
|
||||
});
|
||||
|
||||
export const clickImportWorkspace = async (page: Page) => {
|
||||
const titleBar = buildTitleBarLocators(page);
|
||||
await test.step('Open workspace menu and click "Import workspace"', async () => {
|
||||
await titleBar.workspaceMenuTrigger().click();
|
||||
await titleBar.importWorkspaceOption().click();
|
||||
});
|
||||
};
|
||||
110
tests/utils/page/workspace/import-workspace.ts
Normal file
110
tests/utils/page/workspace/import-workspace.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import AdmZip from 'adm-zip';
|
||||
import * as path from 'path';
|
||||
import { clickImportWorkspace } from '../title-bar';
|
||||
import { test, expect, Page, Locator, ElectronApplication, waitForReadyPage } from '../../../../playwright';
|
||||
|
||||
/**
|
||||
* Import Workspace modal locators.
|
||||
*/
|
||||
export const buildImportWorkspaceModalLocators = (page: Page) => {
|
||||
// Scope every modal query to the dialog so we avoid the brittle
|
||||
const modal = () => page.getByRole('dialog').filter({ hasText: 'Import Workspace' });
|
||||
|
||||
return {
|
||||
// Import Workspace modal
|
||||
modal,
|
||||
fileInput: () => modal().getByTestId('import-workspace-file-input'),
|
||||
selectedFileName: (name: string) => modal().getByText(name),
|
||||
removeFileButton: () => modal().getByText('Remove'),
|
||||
locationInput: () => modal().getByLabel('Extract Location'),
|
||||
browseLink: () => modal().getByText('Browse', { exact: true }),
|
||||
importButton: () => modal().getByTestId('modal-submit-btn')
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid Bruno workspace zip on disk that the importer will accept.
|
||||
* The zip contains a single `workspace.yml` (info.name + info.type: workspace).
|
||||
*
|
||||
* @param zipDir - directory in which to write the zip
|
||||
* @param workspaceName - the workspace name embedded in workspace.yml
|
||||
* @returns absolute path to the created zip file
|
||||
*/
|
||||
export const createWorkspaceZip = (zipDir: string, workspaceName: string): string => {
|
||||
const workspaceYml = [
|
||||
'opencollection: 1.0.0',
|
||||
'info:',
|
||||
` name: "${workspaceName}"`,
|
||||
' type: workspace',
|
||||
'',
|
||||
'collections: []',
|
||||
'specs: []',
|
||||
'docs: \'\'',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
const zip = new AdmZip();
|
||||
zip.addFile('workspace.yml', Buffer.from(workspaceYml, 'utf8'));
|
||||
|
||||
const zipPath = path.join(zipDir, `${workspaceName}.zip`);
|
||||
zip.writeZip(zipPath);
|
||||
return zipPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the workspace dropdown and launch the Import Workspace modal.
|
||||
*/
|
||||
export const openImportWorkspaceModal = async (page: Page) => {
|
||||
const locators = buildImportWorkspaceModalLocators(page);
|
||||
await clickImportWorkspace(page);
|
||||
await locators.modal().waitFor({ state: 'visible' });
|
||||
};
|
||||
|
||||
type ImportWorkspaceOptions = {
|
||||
zipPath: string;
|
||||
/**
|
||||
* Where to extract the workspace. When omitted, the modal's pre-filled
|
||||
* default location (from preferences.general.defaultLocation) is used as-is.
|
||||
*/
|
||||
extractLocation?: string;
|
||||
app?: ElectronApplication;
|
||||
};
|
||||
|
||||
/**
|
||||
* select the zip, ensure an extract location is set, and click Import.
|
||||
*/
|
||||
export const submitWorkspaceImport = async (page: Page, opts: ImportWorkspaceOptions) => {
|
||||
const locators = buildImportWorkspaceModalLocators(page);
|
||||
|
||||
await test.step('Select the workspace zip file', async () => {
|
||||
await locators.fileInput().setInputFiles(opts.zipPath);
|
||||
await expect(locators.selectedFileName(path.basename(opts.zipPath))).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Ensure an extract location is set', async () => {
|
||||
if (opts.extractLocation && opts.app) {
|
||||
// Stub the directory picker so Browse resolves to the desired location.
|
||||
await opts.app.evaluate(({ dialog }, target: string) => {
|
||||
(dialog as { showOpenDialog: typeof dialog.showOpenDialog }).showOpenDialog = () =>
|
||||
Promise.resolve({ canceled: false, filePaths: [target] });
|
||||
}, opts.extractLocation);
|
||||
await locators.locationInput().click();
|
||||
await expect(locators.locationInput()).toHaveValue(opts.extractLocation);
|
||||
} else {
|
||||
// Rely on the pre-filled default location.
|
||||
await expect(locators.locationInput()).not.toHaveValue('');
|
||||
}
|
||||
});
|
||||
|
||||
await test.step('Submit the import', async () => {
|
||||
await locators.importButton().click();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* open the modal and import a zip in one call.
|
||||
*/
|
||||
export const importWorkspaceFromZip = async (page: Page, opts: ImportWorkspaceOptions) => {
|
||||
await openImportWorkspaceModal(page);
|
||||
await submitWorkspaceImport(page, opts);
|
||||
};
|
||||
@@ -24,8 +24,8 @@ function findCreatedWorkspaceDirs(location: string): string[] {
|
||||
}
|
||||
|
||||
test.describe('Create Workspace', () => {
|
||||
test.describe('Inline Creation Flow', () => {
|
||||
test('should create workspace via inline rename and press Enter', async ({ launchElectronApp, createTmpDir }) => {
|
||||
test.describe('inline workspace creation flow', () => {
|
||||
test('TC-957: Verify create a workspace directly from the title bar by typing a name', { tag: '@sanity' }, async ({ launchElectronApp, createTmpDir }) => {
|
||||
const wsLocation = await createTmpDir('ws-location-enter');
|
||||
|
||||
const app = await launchElectronApp({ initUserDataPath, templateVars: { wsLocation } });
|
||||
@@ -454,7 +454,7 @@ test.describe('Create Workspace', () => {
|
||||
await closeElectronApp(app);
|
||||
});
|
||||
|
||||
test('should persist workspace name after app restart', async ({ launchElectronApp, createTmpDir }) => {
|
||||
test('TC-959: Verify created Workspace persists even after bruno app restart.', { tag: '@sanity' }, async ({ launchElectronApp, createTmpDir }) => {
|
||||
const userDataPath = await createTmpDir('create-ws-name-persist');
|
||||
const wsLocation = await createTmpDir('ws-location-persist');
|
||||
|
||||
|
||||
53
tests/workspace/import-workspace/import-workspace.spec.ts
Normal file
53
tests/workspace/import-workspace/import-workspace.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import { test, expect, closeElectronApp, waitForReadyPage } from '../../../playwright';
|
||||
import {
|
||||
createWorkspaceZip,
|
||||
importWorkspaceFromZip
|
||||
} from '../../utils/page/workspace/import-workspace';
|
||||
import { buildTitleBarLocators } from '../../utils/page/title-bar';
|
||||
|
||||
type WorkspaceConfig = {
|
||||
info?: { name: string; type: string };
|
||||
};
|
||||
|
||||
const initUserDataPath = path.join(__dirname, 'init-user-data');
|
||||
|
||||
test.describe('Import Workspace', () => {
|
||||
test('TC-969: Verify Import workspace from local directory containing valid workspace.zip file', { tag: '@sanity' }, async ({ launchElectronApp, createTmpDir }) => {
|
||||
const wsLocation = await createTmpDir('import-ws-location');
|
||||
const zipDir = await createTmpDir('import-ws-zip');
|
||||
const workspaceName = 'Imported WS';
|
||||
const zipPath = createWorkspaceZip(zipDir, workspaceName);
|
||||
|
||||
const app = await launchElectronApp({ initUserDataPath, templateVars: { wsLocation } });
|
||||
const page = await waitForReadyPage(app);
|
||||
const titleBar = buildTitleBarLocators(page);
|
||||
|
||||
await test.step('Import the workspace zip via the Import Workspace modal', async () => {
|
||||
// extractLocation isn't passed as a parameter: the modal pre-fills the seeded default location.
|
||||
await importWorkspaceFromZip(page, { zipPath });
|
||||
});
|
||||
|
||||
await test.step('Verify success toast is shown', async () => {
|
||||
await expect(page.getByText('Workspace imported successfully!')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify the imported workspace becomes the active workspace', async () => {
|
||||
await expect(titleBar.activeWorkspaceName()).toHaveText(workspaceName);
|
||||
});
|
||||
|
||||
await test.step('Verify the workspace was extracted to the filesystem', async () => {
|
||||
const wsDir = path.join(wsLocation, workspaceName);
|
||||
const ymlPath = path.join(wsDir, 'workspace.yml');
|
||||
expect(fs.existsSync(ymlPath)).toBe(true);
|
||||
|
||||
const wsConfig = yaml.load(fs.readFileSync(ymlPath, 'utf8')) as WorkspaceConfig;
|
||||
expect(wsConfig?.info?.name).toBe(workspaceName);
|
||||
expect(wsConfig?.info?.type).toBe('workspace');
|
||||
});
|
||||
|
||||
await closeElectronApp(app);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"preferences": {
|
||||
"general": {
|
||||
"defaultLocation": "{{wsLocation}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user