summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2025-09-26 17:37:38 +0200
committerOskar <oskar@mullvad.net>2025-09-30 09:37:18 +0200
commitb36e8ae73ec5e58e40f73002f4f3bd8f0892d21f (patch)
treee7c0eddb5594b9d47c10dc07dc0db5eada2698fd
parentfb1271119cc886afbcdc7c4c0657ed1fd3445185 (diff)
downloadmullvadvpn-b36e8ae73ec5e58e40f73002f4f3bd8f0892d21f.tar.xz
mullvadvpn-b36e8ae73ec5e58e40f73002f4f3bd8f0892d21f.zip
Improve navigation utils
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/installed/state-dependent/api-access-methods.spec.ts4
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/feature-indicators/feature-indicators.spec.ts8
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/route-object-models/navigation/navigation-object-model.ts3
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/utils.ts90
4 files changed, 31 insertions, 74 deletions
diff --git a/desktop/packages/mullvad-vpn/test/e2e/installed/state-dependent/api-access-methods.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/installed/state-dependent/api-access-methods.spec.ts
index c9d38988d8..c6719cce47 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/installed/state-dependent/api-access-methods.spec.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/installed/state-dependent/api-access-methods.spec.ts
@@ -59,7 +59,7 @@ test('App should display access methods', async () => {
test('App should add invalid access method', async () => {
await page.locator('button:has-text("Add")').click();
- await util.waitForNextRoute();
+ await util.waitForRoute(RoutePath.editApiAccessMethods);
const title = page.locator('h1');
await expect(title).toHaveText('Add method');
@@ -113,7 +113,7 @@ test('App should edit access method', async () => {
const customMethod = page.getByTestId('access-method').last();
await customMethod.locator('button').last().click();
await customMethod.getByText('Edit').click();
- await util.waitForNextRoute();
+ await util.waitForRoute(RoutePath.editApiAccessMethods);
const title = page.locator('h1');
await expect(title).toHaveText('Edit method');
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/feature-indicators/feature-indicators.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/feature-indicators/feature-indicators.spec.ts
index 98b43bcf7f..7a3bce2af6 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/feature-indicators/feature-indicators.spec.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/feature-indicators/feature-indicators.spec.ts
@@ -141,6 +141,8 @@ const featureIndicatorWithOption: FeatureIndicatorWithOptionTestOption[] = [
},
];
+test.describe.configure({ mode: 'parallel' });
+
test.describe('Feature indicators', () => {
test.beforeAll(async () => {
({ page, util } = await startMockedApp());
@@ -246,8 +248,7 @@ test.describe('Feature indicators', () => {
await helpers.connectWithFeatures([featureIndicator]);
await clickFeatureIndicator(featureIndicatorLabel, route);
- const currentRoute = await util.currentRoute();
- expect(currentRoute).toBe(route);
+ await util.waitForRoute(route);
});
},
);
@@ -271,8 +272,7 @@ test.describe('Feature indicators', () => {
}
await expect(element).toBeInViewport();
- const currentRoute = await util.currentRoute();
- expect(currentRoute).toBe(route);
+ await util.waitForRoute(route);
});
},
);
diff --git a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/navigation/navigation-object-model.ts b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/navigation/navigation-object-model.ts
index 3234905965..751551d22d 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/route-object-models/navigation/navigation-object-model.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/route-object-models/navigation/navigation-object-model.ts
@@ -14,8 +14,7 @@ export class NavigationObjectModel {
}
async goBack() {
- await this.navigationSelectors.backButton().click();
- await this.utils.waitForNextRoute();
+ await this.utils.waitForRouteChange(() => this.navigationSelectors.backButton().click());
}
async gotoRoot() {
diff --git a/desktop/packages/mullvad-vpn/test/e2e/utils.ts b/desktop/packages/mullvad-vpn/test/e2e/utils.ts
index 3d38577c41..b7065f6c62 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/utils.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/utils.ts
@@ -1,22 +1,21 @@
+import { expect } from '@playwright/test';
import fs from 'fs';
import { _electron as electron, ElectronApplication, Locator, Page } from 'playwright';
+import { RoutePath } from '../../src/shared/routes';
+
export interface StartAppResponse {
app: ElectronApplication;
page: Page;
util: TestUtils;
}
+type TriggerFn = () => Promise<void> | void;
+
export interface TestUtils {
currentRoute: () => Promise<string | null>;
- waitForNavigation: (initiateNavigation?: () => Promise<void> | void) => Promise<string>;
- waitForRoute: (route: string) => Promise<void>;
- waitForNextRoute: () => Promise<string>;
-}
-
-interface History {
- entries: Array<{ pathname: string }>;
- index: number;
+ waitForRoute: (route: RoutePath) => Promise<void>;
+ waitForRouteChange: (trigger: TriggerFn) => Promise<void>;
}
type LaunchOptions = NonNullable<Parameters<typeof electron.launch>[0]>;
@@ -24,15 +23,15 @@ type LaunchOptions = NonNullable<Parameters<typeof electron.launch>[0]>;
export const startApp = async (options: LaunchOptions): Promise<StartAppResponse> => {
const app = await launch(options);
const page = await app.firstWindow();
+ await page.waitForEvent('load');
page.on('pageerror', (error) => console.log(error));
page.on('console', (msg) => console.log(msg.text()));
const util: TestUtils = {
- currentRoute: currentRouteFactory(app),
- waitForNavigation: waitForNavigationFactory(app),
- waitForRoute: waitForRouteFactory(app),
- waitForNextRoute: waitForNextRouteFactory(app),
+ currentRoute: () => currentRoute(page),
+ waitForRoute: (route: RoutePath) => waitForRoute(page, route),
+ waitForRouteChange: (trigger: TriggerFn) => waitForRouteChange(page, trigger),
};
return { app, page, util };
@@ -43,62 +42,21 @@ export const launch = (options: LaunchOptions): Promise<ElectronApplication> =>
return electron.launch(options);
};
-const currentRouteFactory = (app: ElectronApplication) => {
- return () => {
- return app.evaluate<string | null>(({ webContents }) => {
- const electronWebContent = webContents
- .getAllWebContents()
- // Select window that isn't devtools
- .find((webContents) => webContents.getURL().startsWith('file://'));
-
- if (electronWebContent) {
- return electronWebContent.executeJavaScript('window.e2e.location');
- }
-
- return null;
- });
- };
-};
-
-const waitForNavigationFactory = (app: ElectronApplication) => {
- const waitForNextRoute = waitForNextRouteFactory(app);
- // Wait for navigation animation to finish. A function can be provided that initiates the
- // navigation, e.g. clicks a button.
- return async (initiateNavigation?: () => Promise<void> | void) => {
- // Wait for route to change after optionally initiating the navigation.
- const [route] = await Promise.all([waitForNextRoute(), initiateNavigation?.()]);
-
- return route;
- };
-};
-
-// This factory returns a function which returns a boolean when the route passed to it matches that of the application.
-const waitForRouteFactory = (app: ElectronApplication) => {
- const getCurrentRoute = currentRouteFactory(app);
-
- const waitForRoute = async (route: string) => {
- const currentRoute = await getCurrentRoute();
-
- if (currentRoute !== route) {
- return waitForRoute(route);
- }
- };
+function currentRoute(page: Page): Promise<string | null> {
+ return page.evaluate('window.e2e.location');
+}
- return waitForRoute;
-};
+// Returns a promise which resolves when the provided route is reached.
+async function waitForRoute(page: Page, expectedRoute: RoutePath): Promise<void> {
+ await expect.poll(async () => currentRoute(page)).toBe(expectedRoute);
+}
-// Returns the route when it changes
-const waitForNextRouteFactory = (app: ElectronApplication) => {
- return async () =>
- app.evaluate<string>(
- ({ ipcMain }) =>
- new Promise((resolve) => {
- ipcMain.once('navigation-setHistory', (_event, history: History) => {
- resolve(history.entries[history.index].pathname);
- });
- }),
- );
-};
+// Returns a promise which resolves when the route changes.
+async function waitForRouteChange(page: Page, trigger: TriggerFn) {
+ const initialRoute = await currentRoute(page);
+ await trigger();
+ await expect.poll(async () => currentRoute(page)).not.toBe(initialRoute);
+}
const getStyleProperty = (locator: Locator, property: string) => {
return locator.evaluate(