mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Chore: Tests: Improve Playwright test reliability (#10981)
This commit is contained in:
parent
0b13dbddd8
commit
e41394b57f
@ -13,11 +13,16 @@ test.describe('markdownEditor', () => {
|
|||||||
await mainScreen.importHtmlDirectory(electronApp, join(__dirname, 'resources', 'html-import'));
|
await mainScreen.importHtmlDirectory(electronApp, join(__dirname, 'resources', 'html-import'));
|
||||||
const importedFolder = mainScreen.sidebar.container.getByText('html-import');
|
const importedFolder = mainScreen.sidebar.container.getByText('html-import');
|
||||||
await importedFolder.waitFor();
|
await importedFolder.waitFor();
|
||||||
await importedFolder.click();
|
|
||||||
|
|
||||||
await mainScreen.noteList.focusContent(electronApp);
|
// Retry -- focusing the imported-folder may fail in some cases
|
||||||
const importedHtmlFileItem = mainScreen.noteList.getNoteItemByTitle('test-html-file-with-image');
|
await expect(async () => {
|
||||||
await importedHtmlFileItem.click();
|
await importedFolder.click();
|
||||||
|
|
||||||
|
await mainScreen.noteList.focusContent(electronApp);
|
||||||
|
|
||||||
|
const importedHtmlFileItem = mainScreen.noteList.getNoteItemByTitle('test-html-file-with-image');
|
||||||
|
await importedHtmlFileItem.click({ timeout: 300 });
|
||||||
|
}).toPass();
|
||||||
|
|
||||||
const viewerFrame = mainScreen.noteEditor.getNoteViewerIframe();
|
const viewerFrame = mainScreen.noteEditor.getNoteViewerIframe();
|
||||||
// Should render headers
|
// Should render headers
|
||||||
|
@ -14,10 +14,7 @@ export default class GoToAnything {
|
|||||||
|
|
||||||
public async open(electronApp: ElectronApplication) {
|
public async open(electronApp: ElectronApplication) {
|
||||||
await this.mainScreen.waitFor();
|
await this.mainScreen.waitFor();
|
||||||
|
await activateMainMenuItem(electronApp, 'Goto Anything...');
|
||||||
if (!await activateMainMenuItem(electronApp, 'Goto Anything...')) {
|
|
||||||
throw new Error('Menu item for opening Goto Anything not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.waitFor();
|
return this.waitFor();
|
||||||
}
|
}
|
||||||
|
@ -46,12 +46,7 @@ export default class MainScreen {
|
|||||||
|
|
||||||
public async openSettings(electronApp: ElectronApplication) {
|
public async openSettings(electronApp: ElectronApplication) {
|
||||||
// Check both labels so this works on MacOS
|
// Check both labels so this works on MacOS
|
||||||
const openedWithPreferences = await activateMainMenuItem(electronApp, 'Preferences...');
|
await activateMainMenuItem(electronApp, /^(Preferences\.\.\.|Options)$/);
|
||||||
const openedWithOptions = await activateMainMenuItem(electronApp, 'Options');
|
|
||||||
|
|
||||||
if (!openedWithOptions && !openedWithPreferences) {
|
|
||||||
throw new Error('Unable to find settings menu item in application menus.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async search(text: string) {
|
public async search(text: string) {
|
||||||
@ -61,10 +56,6 @@ export default class MainScreen {
|
|||||||
|
|
||||||
public async importHtmlDirectory(electronApp: ElectronApplication, path: string) {
|
public async importHtmlDirectory(electronApp: ElectronApplication, path: string) {
|
||||||
await setFilePickerResponse(electronApp, [path]);
|
await setFilePickerResponse(electronApp, [path]);
|
||||||
const startedImport = await activateMainMenuItem(electronApp, 'HTML - HTML document (Directory)', 'Import');
|
await activateMainMenuItem(electronApp, 'HTML - HTML document (Directory)', 'Import');
|
||||||
|
|
||||||
if (!startedImport) {
|
|
||||||
throw new Error('Unable to find HTML directory import menu item.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ import { ElectronApplication, Locator, Page, expect } from '@playwright/test';
|
|||||||
|
|
||||||
export default class NoteList {
|
export default class NoteList {
|
||||||
public readonly container: Locator;
|
public readonly container: Locator;
|
||||||
|
public readonly sortOrderButton: Locator;
|
||||||
|
|
||||||
public constructor(page: Page) {
|
public constructor(page: Page) {
|
||||||
this.container = page.locator('.rli-noteList');
|
this.container = page.locator('.rli-noteList');
|
||||||
|
this.sortOrderButton = this.container.getByRole('button', { name: 'Toggle sort order' });
|
||||||
}
|
}
|
||||||
|
|
||||||
public waitFor() {
|
public waitFor() {
|
||||||
@ -13,14 +15,12 @@ export default class NoteList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async sortBy(electronApp: ElectronApplication, sortMethod: string) {
|
private async sortBy(electronApp: ElectronApplication, sortMethod: string) {
|
||||||
const success = await activateMainMenuItem(electronApp, sortMethod, 'Sort notes by');
|
await activateMainMenuItem(electronApp, sortMethod, 'Sort notes by');
|
||||||
if (!success) {
|
|
||||||
throw new Error(`Unable to find sorting menu item: ${sortMethod}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sortByTitle(electronApp: ElectronApplication) {
|
public async sortByTitle(electronApp: ElectronApplication) {
|
||||||
return this.sortBy(electronApp, 'Title');
|
await this.sortBy(electronApp, 'Title');
|
||||||
|
await expect(this.sortOrderButton).toHaveAttribute('title', /Toggle sort order field:[\n ]*title ->/);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async focusContent(electronApp: ElectronApplication) {
|
public async focusContent(electronApp: ElectronApplication) {
|
||||||
|
@ -23,10 +23,7 @@ export default class Sidebar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async sortBy(electronApp: ElectronApplication, option: string) {
|
private async sortBy(electronApp: ElectronApplication, option: string) {
|
||||||
const success = await activateMainMenuItem(electronApp, option, 'Sort notebooks by');
|
await activateMainMenuItem(electronApp, option, 'Sort notebooks by');
|
||||||
if (!success) {
|
|
||||||
throw new Error(`Failed to find menu item: ${option}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sortByDate(electronApp: ElectronApplication) {
|
public async sortByDate(electronApp: ElectronApplication) {
|
||||||
|
@ -91,7 +91,14 @@ test.describe('noteList', () => {
|
|||||||
await noteList.focusContent(electronApp);
|
await noteList.focusContent(electronApp);
|
||||||
// The most recently-created note should be visible
|
// The most recently-created note should be visible
|
||||||
const note4Item = noteList.getNoteItemByTitle('note_4');
|
const note4Item = noteList.getNoteItemByTitle('note_4');
|
||||||
|
const note3Item = noteList.getNoteItemByTitle('note_3');
|
||||||
|
const note2Item = noteList.getNoteItemByTitle('note_2');
|
||||||
|
const note1Item = noteList.getNoteItemByTitle('note_1');
|
||||||
await expect(note4Item).toBeVisible();
|
await expect(note4Item).toBeVisible();
|
||||||
|
await expect(note3Item).toBeVisible();
|
||||||
|
await expect(note2Item).toBeVisible();
|
||||||
|
await expect(note1Item).toBeVisible();
|
||||||
|
|
||||||
await noteList.expectNoteToBeSelected('note_4');
|
await noteList.expectNoteToBeSelected('note_4');
|
||||||
|
|
||||||
await noteList.container.press('ArrowUp');
|
await noteList.container.press('ArrowUp');
|
||||||
|
@ -8,7 +8,7 @@ test.describe('settings', () => {
|
|||||||
await mainScreen.waitFor();
|
await mainScreen.waitFor();
|
||||||
|
|
||||||
// Sort order buttons should be visible by default
|
// Sort order buttons should be visible by default
|
||||||
const sortOrderLocator = mainScreen.noteList.container.getByRole('button', { name: 'Toggle sort order' });
|
const sortOrderLocator = mainScreen.noteList.sortOrderButton;
|
||||||
await expect(sortOrderLocator).toBeVisible();
|
await expect(sortOrderLocator).toBeVisible();
|
||||||
|
|
||||||
await mainScreen.openSettings(electronApp);
|
await mainScreen.openSettings(electronApp);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './util/test';
|
import { test } from './util/test';
|
||||||
import MainScreen from './models/MainScreen';
|
import MainScreen from './models/MainScreen';
|
||||||
import SettingsScreen from './models/SettingsScreen';
|
import SettingsScreen from './models/SettingsScreen';
|
||||||
import activateMainMenuItem from './util/activateMainMenuItem';
|
import activateMainMenuItem from './util/activateMainMenuItem';
|
||||||
@ -28,7 +28,7 @@ test.describe('simpleBackup', () => {
|
|||||||
await mainScreen.waitFor();
|
await mainScreen.waitFor();
|
||||||
|
|
||||||
// Backups should work
|
// Backups should work
|
||||||
expect(await activateMainMenuItem(electronApp, 'Create backup')).toBe(true);
|
await activateMainMenuItem(electronApp, 'Create backup');
|
||||||
|
|
||||||
const successDialog = mainWindow.locator('iframe[id$=backup-backupDialog]');
|
const successDialog = mainWindow.locator('iframe[id$=backup-backupDialog]');
|
||||||
await successDialog.waitFor();
|
await successDialog.waitFor();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import type { ElectronApplication } from '@playwright/test';
|
import { expect, type ElectronApplication } from '@playwright/test';
|
||||||
import type { MenuItem } from 'electron';
|
import type { MenuItem } from 'electron';
|
||||||
|
|
||||||
|
|
||||||
@ -7,35 +7,45 @@ import type { MenuItem } from 'electron';
|
|||||||
// https://github.com/spaceagetv/electron-playwright-helpers/blob/main/src/menu_helpers.ts
|
// https://github.com/spaceagetv/electron-playwright-helpers/blob/main/src/menu_helpers.ts
|
||||||
|
|
||||||
// If given, `parentMenuLabel` should be the label of the menu containing the target item.
|
// If given, `parentMenuLabel` should be the label of the menu containing the target item.
|
||||||
const activateMainMenuItem = (
|
const activateMainMenuItem = async (
|
||||||
electronApp: ElectronApplication,
|
electronApp: ElectronApplication,
|
||||||
targetItemLabel: string,
|
targetItemLabel: string|RegExp,
|
||||||
parentMenuLabel?: string,
|
parentMenuLabel?: string,
|
||||||
) => {
|
) => {
|
||||||
return electronApp.evaluate(async ({ Menu }, [targetItemLabel, parentMenuLabel]) => {
|
await expect.poll(() => {
|
||||||
const activateItemInSubmenu = (submenu: MenuItem[], parentLabel: string) => {
|
return electronApp.evaluate(async ({ Menu }, [targetItemLabel, parentMenuLabel]) => {
|
||||||
for (const item of submenu) {
|
const activateItemInSubmenu = (submenu: MenuItem[], parentLabel: string) => {
|
||||||
const matchesParent = !parentMenuLabel || parentLabel === parentMenuLabel;
|
for (const item of submenu) {
|
||||||
if (item.label === targetItemLabel && matchesParent && item.visible) {
|
const matchesParent = !parentMenuLabel || parentLabel === parentMenuLabel;
|
||||||
// Found!
|
const matchesLabel = typeof targetItemLabel === 'string' ? (
|
||||||
item.click();
|
targetItemLabel === item.label
|
||||||
return true;
|
) : (
|
||||||
} else if (item.submenu) {
|
item.label.match(targetItemLabel)
|
||||||
const foundItem = activateItemInSubmenu(item.submenu.items, item.label);
|
);
|
||||||
|
|
||||||
if (foundItem) {
|
if (matchesLabel && matchesParent && item.visible) {
|
||||||
|
// Found!
|
||||||
|
item.click();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.submenu) {
|
||||||
|
const foundItem = activateItemInSubmenu(item.submenu.items, item.label);
|
||||||
|
|
||||||
|
if (foundItem) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No item found
|
// No item found
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const appMenu = Menu.getApplicationMenu();
|
const appMenu = Menu.getApplicationMenu();
|
||||||
return activateItemInSubmenu(appMenu.items, '');
|
return activateItemInSubmenu(appMenu.items, '');
|
||||||
}, [targetItemLabel, parentMenuLabel]);
|
}, [targetItemLabel, parentMenuLabel]);
|
||||||
|
}, {
|
||||||
|
message: `should find and activate menu item with label ${JSON.stringify(targetItemLabel)}`,
|
||||||
|
}).toBe(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default activateMainMenuItem;
|
export default activateMainMenuItem;
|
||||||
|
@ -56,6 +56,13 @@ export const test = base.extend<JoplinFixtures>({
|
|||||||
|
|
||||||
mainWindow: async ({ electronApp }, use) => {
|
mainWindow: async ({ electronApp }, use) => {
|
||||||
const mainWindow = await firstNonDevToolsWindow(electronApp);
|
const mainWindow = await firstNonDevToolsWindow(electronApp);
|
||||||
|
|
||||||
|
// Setting the viewport size helps keep test environments consistent.
|
||||||
|
await mainWindow.setViewportSize({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
});
|
||||||
|
|
||||||
await use(mainWindow);
|
await use(mainWindow);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user