summaryrefslogtreecommitdiffhomepage
path: root/gui/test/e2e
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-09-20 16:29:26 +0200
committerOskar <oskar@mullvad.net>2024-09-24 13:18:18 +0200
commit4bb25616f02af4e15b0140767c07b61e49e8aa83 (patch)
treeed5371911d23ba2bab8e701f351d822202d63d57 /gui/test/e2e
parent659aa11f05ae2b278eef6ab0ef5bc537e756109a (diff)
downloadmullvadvpn-4bb25616f02af4e15b0140767c07b61e49e8aa83.tar.xz
mullvadvpn-4bb25616f02af4e15b0140767c07b61e49e8aa83.zip
Fix linting errors
Diffstat (limited to 'gui/test/e2e')
-rw-r--r--gui/test/e2e/installed/installed-utils.ts2
-rw-r--r--gui/test/e2e/installed/playwright.config.ts2
-rw-r--r--gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts30
-rw-r--r--gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts73
-rw-r--r--gui/test/e2e/installed/state-dependent/device-revoked.spec.ts8
-rw-r--r--gui/test/e2e/installed/state-dependent/disconnected.spec.ts2
-rw-r--r--gui/test/e2e/installed/state-dependent/location.spec.ts3
-rw-r--r--gui/test/e2e/installed/state-dependent/login.spec.ts72
-rw-r--r--gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts29
-rw-r--r--gui/test/e2e/installed/state-dependent/obfuscation.spec.ts36
-rw-r--r--gui/test/e2e/installed/state-dependent/settings-import.spec.ts64
-rw-r--r--gui/test/e2e/installed/state-dependent/too-many-devices.spec.ts19
-rw-r--r--gui/test/e2e/installed/state-dependent/tunnel-state.spec.ts16
-rw-r--r--gui/test/e2e/mocked/expired-account-error-view.spec.ts23
-rw-r--r--gui/test/e2e/mocked/feature-indicators.spec.ts39
-rw-r--r--gui/test/e2e/mocked/mocked-utils.ts4
-rw-r--r--gui/test/e2e/mocked/settings.spec.ts4
-rw-r--r--gui/test/e2e/mocked/tunnel-state.spec.ts15
-rw-r--r--gui/test/e2e/shared/tunnel-state.ts1
-rw-r--r--gui/test/e2e/utils.ts57
20 files changed, 252 insertions, 247 deletions
diff --git a/gui/test/e2e/installed/installed-utils.ts b/gui/test/e2e/installed/installed-utils.ts
index 1a8cd4258e..9e4dd6c79c 100644
--- a/gui/test/e2e/installed/installed-utils.ts
+++ b/gui/test/e2e/installed/installed-utils.ts
@@ -2,7 +2,7 @@ import { startApp } from '../utils';
export const startInstalledApp = async (): ReturnType<typeof startApp> => {
return startApp({ executablePath: getAppInstallPath() });
-}
+};
function getAppInstallPath(): string {
switch (process.platform) {
diff --git a/gui/test/e2e/installed/playwright.config.ts b/gui/test/e2e/installed/playwright.config.ts
index 9c1fa7fb87..9487e06905 100644
--- a/gui/test/e2e/installed/playwright.config.ts
+++ b/gui/test/e2e/installed/playwright.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig } from "@playwright/test";
+import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: process.cwd(),
diff --git a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts
index 0d6e8a0672..d17611261b 100644
--- a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts
@@ -1,9 +1,9 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { startInstalledApp } from '../installed-utils';
-import { TestUtils } from '../../utils';
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { TestUtils } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
// This test expects the daemon to be logged in and only have "Direct" and "Mullvad Bridges"
// access methods.
@@ -31,10 +31,10 @@ test.afterAll(async () => {
});
async function navigateToAccessMethods() {
- await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
- await util.waitForNavigation(async () => await page.getByText('API access').click());
+ await util.waitForNavigation(() => page.click('button[aria-label="Settings"]'));
+ await util.waitForNavigation(() => page.getByText('API access').click());
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('API access');
}
@@ -52,9 +52,9 @@ test('App should display access methods', async () => {
});
test('App should add invalid access method', async () => {
- await util.waitForNavigation(async () => await page.locator('button:has-text("Add")').click());
+ await util.waitForNavigation(() => page.locator('button:has-text("Add")').click());
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Add method');
const inputs = page.locator('input');
@@ -71,13 +71,13 @@ test('App should add invalid access method', async () => {
await inputs.nth(2).fill(process.env.SHADOWSOCKS_SERVER_PORT!);
await expect(addButton).toBeEnabled();
- await addButton.click()
+ await addButton.click();
await expect(page.getByText('Testing method...')).toBeVisible();
await expect(page.getByText('API unreachable, add anyway?')).toBeVisible();
expect(
- await util.waitForNavigation(async () => await page.locator('button:has-text("Save")').click())
+ await util.waitForNavigation(() => page.locator('button:has-text("Save")').click()),
).toEqual(RoutePath.apiAccessMethods);
const accessMethods = page.getByTestId('access-method');
@@ -107,7 +107,7 @@ test('App should edit access method', async () => {
await customMethod.locator('button').last().click();
await util.waitForNavigation(() => customMethod.getByText('Edit').click());
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Edit method');
const inputs = page.locator('input');
@@ -125,11 +125,13 @@ test('App should edit access method', async () => {
await inputs.nth(3).fill(process.env.SHADOWSOCKS_SERVER_PASSWORD!);
await page.getByTestId('ciphers').click();
- await page.getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true }).click();
+ await page
+ .getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true })
+ .click();
- expect(
- await util.waitForNavigation(async () => await saveButton.click())
- ).toEqual(RoutePath.apiAccessMethods);
+ expect(await util.waitForNavigation(() => saveButton.click())).toEqual(
+ RoutePath.apiAccessMethods,
+ );
const accessMethods = page.getByTestId('access-method');
await expect(accessMethods).toHaveCount(3);
diff --git a/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts b/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts
index 5efd110213..34efb60f26 100644
--- a/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/custom-bridge.spec.ts
@@ -1,10 +1,10 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { startInstalledApp } from '../installed-utils';
-import { TestUtils } from '../../utils';
import { colors } from '../../../../src/config.json';
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { TestUtils } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
// This test expects the daemon to be logged in and not have a custom bridge configured.
// Env parameters:
@@ -25,37 +25,35 @@ test.afterAll(async () => {
});
test('App should enable bridge mode', async () => {
- await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
- expect(
- await util.waitForNavigation(async () => await page.getByText('VPN settings').click()),
- ).toBe(RoutePath.vpnSettings);
+ await util.waitForNavigation(() => page.click('button[aria-label="Settings"]'));
+ expect(await util.waitForNavigation(() => page.getByText('VPN settings').click())).toBe(
+ RoutePath.vpnSettings,
+ );
await page.getByRole('option', { name: 'OpenVPN' }).click();
- expect(
- await util.waitForNavigation(async () => await page.getByText('OpenVPN settings').click()),
- ).toBe(RoutePath.openVpnSettings);
+ expect(await util.waitForNavigation(() => page.getByText('OpenVPN settings').click())).toBe(
+ RoutePath.openVpnSettings,
+ );
await page.getByTestId('bridge-mode-on').click();
await expect(page.getByText('Enable bridge mode?')).toBeVisible();
- page.getByTestId('enable-confirm').click();
+ await page.getByTestId('enable-confirm').click();
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'));
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'));
- expect(
- await util.waitForNavigation(async () => await page.click('button[aria-label="Close"]')),
- ).toBe(RoutePath.main);
+ await util.waitForNavigation(() => page.click('button[aria-label="Back"]'));
+ await util.waitForNavigation(() => page.click('button[aria-label="Back"]'));
+ expect(await util.waitForNavigation(() => page.click('button[aria-label="Close"]'))).toBe(
+ RoutePath.main,
+ );
});
test('App display disabled custom bridge', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label^="Select location"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label^="Select location"]')),
).toBe(RoutePath.selectLocation);
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Select location');
await page.getByText(/^Entry$/).click();
@@ -66,12 +64,10 @@ test('App display disabled custom bridge', async () => {
test('App should add new custom bridge', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="Add new custom bridge"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="Add new custom bridge"]')),
).toBe(RoutePath.editCustomBridge);
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Add custom bridge');
const inputs = page.locator('input');
@@ -88,11 +84,11 @@ test('App should add new custom bridge', async () => {
await inputs.nth(2).fill(process.env.SHADOWSOCKS_SERVER_PASSWORD!);
await page.getByTestId('ciphers').click();
- await page.getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true }).click();
+ await page
+ .getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true })
+ .click();
- expect(
- await util.waitForNavigation(async () => await addButton.click())
- ).toEqual(RoutePath.selectLocation);
+ expect(await util.waitForNavigation(() => addButton.click())).toEqual(RoutePath.selectLocation);
const customBridgeButton = page.getByText('Custom bridge');
await expect(customBridgeButton).toBeEnabled();
@@ -109,11 +105,9 @@ test('App should select custom bridge', async () => {
await page.getByText(/^Entry$/).click();
await expect(customBridgeButton).not.toHaveCSS('background-color', colors.green);
-
await customBridgeButton.click();
await page.getByText(/^Entry$/).click();
await expect(customBridgeButton).toHaveCSS('background-color', colors.green);
-
});
test('App should edit custom bridge', async () => {
@@ -122,12 +116,10 @@ test('App should edit custom bridge', async () => {
await page.getByText(/^Entry$/).click();
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="Edit custom bridge"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="Edit custom bridge"]')),
).toBe(RoutePath.editCustomBridge);
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Edit custom bridge');
const inputs = page.locator('input');
@@ -138,10 +130,7 @@ test('App should edit custom bridge', async () => {
await inputs.nth(1).fill(process.env.SHADOWSOCKS_SERVER_PORT!);
await expect(saveButton).toBeEnabled();
-
- expect(
- await util.waitForNavigation(async () => await saveButton.click())
- ).toEqual(RoutePath.selectLocation);
+ expect(await util.waitForNavigation(() => saveButton.click())).toEqual(RoutePath.selectLocation);
const customBridgeButton = page.locator('button:has-text("Custom bridge")');
await expect(customBridgeButton).toBeEnabled();
@@ -150,9 +139,7 @@ test('App should edit custom bridge', async () => {
test('App should delete custom bridge', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="Edit custom bridge"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="Edit custom bridge"]')),
).toBe(RoutePath.editCustomBridge);
const deleteButton = page.locator('button:has-text("Delete")');
@@ -163,9 +150,9 @@ test('App should delete custom bridge', async () => {
await expect(page.getByText('Delete custom bridge?')).toBeVisible();
const confirmButton = page.getByTestId('delete-confirm');
- expect(
- await util.waitForNavigation(async () => await confirmButton.click())
- ).toEqual(RoutePath.selectLocation);
+ expect(await util.waitForNavigation(() => confirmButton.click())).toEqual(
+ RoutePath.selectLocation,
+ );
const customBridgeButton = page.locator('button:has-text("Custom bridge")');
await expect(customBridgeButton).toBeDisabled();
diff --git a/gui/test/e2e/installed/state-dependent/device-revoked.spec.ts b/gui/test/e2e/installed/state-dependent/device-revoked.spec.ts
index 22aa30b3d5..2b9f8d0c58 100644
--- a/gui/test/e2e/installed/state-dependent/device-revoked.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/device-revoked.spec.ts
@@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
+
import { RoutePath } from '../../../../src/renderer/lib/routes';
import { TestUtils } from '../../utils';
-
import { startInstalledApp } from '../installed-utils';
// This test expects the daemon to be logged in to a revoked device.
@@ -23,7 +23,7 @@ test('App should fail to login', async () => {
await expect(page.getByTestId('title')).toHaveText('Device is inactive');
- expect(await util.waitForNavigation(() => {
- page.getByText('Go to login').click();
- })).toEqual(RoutePath.login);
+ expect(await util.waitForNavigation(() => page.getByText('Go to login').click())).toEqual(
+ RoutePath.login,
+ );
});
diff --git a/gui/test/e2e/installed/state-dependent/disconnected.spec.ts b/gui/test/e2e/installed/state-dependent/disconnected.spec.ts
index 360a88338d..253545dad7 100644
--- a/gui/test/e2e/installed/state-dependent/disconnected.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/disconnected.spec.ts
@@ -1,7 +1,7 @@
import { test } from '@playwright/test';
import { Page } from 'playwright';
-import { expectDisconnected } from '../../shared/tunnel-state';
+import { expectDisconnected } from '../../shared/tunnel-state';
import { startInstalledApp } from '../installed-utils';
// This test expects the daemon to be logged into an account that has time left and to be
diff --git a/gui/test/e2e/installed/state-dependent/location.spec.ts b/gui/test/e2e/installed/state-dependent/location.spec.ts
index 229adb9acc..f0bd2e11f7 100644
--- a/gui/test/e2e/installed/state-dependent/location.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/location.spec.ts
@@ -20,7 +20,6 @@ test('App should have a country', async () => {
await expect(countryLabel).not.toBeEmpty();
const cityLabel = page.getByTestId('city');
- const noCityLabel = await cityLabel.count() === 0;
+ const noCityLabel = (await cityLabel.count()) === 0;
expect(noCityLabel).toBeTruthy();
});
-
diff --git a/gui/test/e2e/installed/state-dependent/login.spec.ts b/gui/test/e2e/installed/state-dependent/login.spec.ts
index ba72a5b466..ad49c88edd 100644
--- a/gui/test/e2e/installed/state-dependent/login.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/login.spec.ts
@@ -1,11 +1,11 @@
-import { exec, execSync } from 'child_process';
import { expect, test } from '@playwright/test';
+import { exec, execSync } from 'child_process';
import { Locator, Page } from 'playwright';
+
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { expectDisconnected } from '../../shared/tunnel-state';
import { TestUtils } from '../../utils';
-
import { startInstalledApp } from '../installed-utils';
-import { expectDisconnected } from '../../shared/tunnel-state';
// This test expects the daemon to be logged out.
// Env parameters:
@@ -27,7 +27,7 @@ test.afterAll(async () => {
test('App should fail to login', async () => {
expect(await util.currentRoute()).toEqual(RoutePath.login);
- const title = page.locator('h1')
+ const title = page.locator('h1');
const subtitle = page.getByTestId('subtitle');
const loginInput = getInput(page);
@@ -46,15 +46,17 @@ test('App should fail to login', async () => {
test('App should create account', async () => {
expect(await util.currentRoute()).toEqual(RoutePath.login);
- const title = page.locator('h1')
+ const title = page.locator('h1');
const subtitle = page.getByTestId('subtitle');
- expect(await util.waitForNavigation(async () => {
- await page.getByText('Create account').click();
+ expect(
+ await util.waitForNavigation(async () => {
+ await page.getByText('Create account').click();
- await expect(title).toHaveText('Account created');
- await expect(subtitle).toHaveText('Logged in');
- })).toEqual(RoutePath.expired);
+ await expect(title).toHaveText('Account created');
+ await expect(subtitle).toHaveText('Logged in');
+ }),
+ ).toEqual(RoutePath.expired);
const outOfTimeTitle = page.getByTestId('title');
await expect(outOfTimeTitle).toHaveText('Congrats!');
@@ -65,15 +67,17 @@ test('App should create account', async () => {
});
test('App should become logged out', async () => {
- expect(await util.waitForNavigation(() => {
- exec('mullvad account logout');
- })).toEqual(RoutePath.login);
+ expect(
+ await util.waitForNavigation(() => {
+ exec('mullvad account logout');
+ }),
+ ).toEqual(RoutePath.login);
});
test('App should log in', async () => {
expect(await util.currentRoute()).toEqual(RoutePath.login);
- const title = page.locator('h1')
+ const title = page.locator('h1');
const subtitle = page.getByTestId('subtitle');
const loginInput = getInput(page);
@@ -82,25 +86,31 @@ test('App should log in', async () => {
await loginInput.fill(process.env.ACCOUNT_NUMBER!);
- expect(await util.waitForNavigation(async () => {
- await loginInput.press('Enter');
+ expect(
+ await util.waitForNavigation(async () => {
+ await loginInput.press('Enter');
- await expect(title).toHaveText('Logged in');
- await expect(subtitle).toHaveText('Valid account number');
- })).toEqual(RoutePath.main);
+ await expect(title).toHaveText('Logged in');
+ await expect(subtitle).toHaveText('Valid account number');
+ }),
+ ).toEqual(RoutePath.main);
await expectDisconnected(page);
});
test('App should log out', async () => {
- expect(await util.waitForNavigation(() => {
- void page.getByTestId('account-button').click();
- })).toEqual(RoutePath.account);
+ expect(
+ await util.waitForNavigation(() => {
+ void page.getByTestId('account-button').click();
+ }),
+ ).toEqual(RoutePath.account);
- expect(await util.waitForNavigation(() => {
- void page.getByText('Log out').click();
- })).toEqual(RoutePath.login);
+ expect(
+ await util.waitForNavigation(() => {
+ void page.getByText('Log out').click();
+ }),
+ ).toEqual(RoutePath.login);
- const title = page.locator('h1')
+ const title = page.locator('h1');
const subtitle = page.getByTestId('subtitle');
await expect(title).toHaveText('Login');
await expect(subtitle).toHaveText('Enter your account number');
@@ -109,7 +119,7 @@ test('App should log out', async () => {
test('App should log in to expired account', async () => {
expect(await util.currentRoute()).toEqual(RoutePath.login);
- const title = page.locator('h1')
+ const title = page.locator('h1');
const subtitle = page.getByTestId('subtitle');
const loginInput = getInput(page);
@@ -118,9 +128,11 @@ test('App should log in to expired account', async () => {
await loginInput.fill(accountNumber);
- expect(await util.waitForNavigation(async () => {
- await loginInput.press('Enter');
- })).toEqual(RoutePath.expired);
+ expect(
+ await util.waitForNavigation(async () => {
+ await loginInput.press('Enter');
+ }),
+ ).toEqual(RoutePath.expired);
const outOfTimeTitle = page.getByTestId('title');
await expect(outOfTimeTitle).toHaveText('Out of time');
diff --git a/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts b/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts
index 0f1680bf44..6d49173bcd 100644
--- a/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/macos-split-tunneling.spec.ts
@@ -1,10 +1,10 @@
-import { Locator, expect, test } from '@playwright/test';
-import { Page } from 'playwright';
+import { expect, Locator, test } from '@playwright/test';
import { execSync } from 'child_process';
+import { Page } from 'playwright';
-import { startInstalledApp } from '../installed-utils';
-import { TestUtils } from '../../utils';
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { TestUtils } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
// macOS only. This test expects the daemon to be logged in and for split tunneling to be off and
// have no split applications.
@@ -21,13 +21,13 @@ test.afterAll(async () => {
});
async function navigateToSplitTunneling() {
- await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
+ await util.waitForNavigation(() => page.click('button[aria-label="Settings"]'));
- expect(
- await util.waitForNavigation(async () => await page.getByText('Split tunneling').click())
- ).toEqual(RoutePath.splitTunneling);
+ expect(await util.waitForNavigation(() => page.getByText('Split tunneling').click())).toEqual(
+ RoutePath.splitTunneling,
+ );
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Split tunneling');
}
@@ -46,7 +46,7 @@ test('App should enable split tunneling', async () => {
const launchPadApp = page.getByText('launchpad');
await expect(launchPadApp).not.toBeVisible();
- toggle.click();
+ await toggle.click();
await expect(toggle).toBeChecked();
await expect(splitList).not.toBeVisible();
await expect(nonSplitList).toBeVisible();
@@ -133,7 +133,7 @@ test('App should disable split tunneling', async () => {
const launchPadApp = page.getByText('launchpad');
await expect(launchPadApp).toBeVisible();
- toggle.click();
+ await toggle.click();
await expect(toggle).not.toBeChecked();
});
@@ -148,7 +148,7 @@ async function numberOfApplicationsInList(listTestid: string) {
return 0;
}
- return await list.locator('button').count();
+ return list.locator('button').count();
}
function getDaemonSplitTunnelingApplications() {
@@ -157,6 +157,7 @@ function getDaemonSplitTunnelingApplications() {
}
function isSplitInDaemon(app: string): boolean {
- return !!getDaemonSplitTunnelingApplications()
- .find((splitApp) => splitApp.toLowerCase().includes(app));
+ return !!getDaemonSplitTunnelingApplications().find((splitApp) =>
+ splitApp.toLowerCase().includes(app),
+ );
}
diff --git a/gui/test/e2e/installed/state-dependent/obfuscation.spec.ts b/gui/test/e2e/installed/state-dependent/obfuscation.spec.ts
index af72ded6d5..b9518f8717 100644
--- a/gui/test/e2e/installed/state-dependent/obfuscation.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/obfuscation.spec.ts
@@ -2,10 +2,10 @@ import { expect, test } from '@playwright/test';
import { execSync } from 'child_process';
import { Page } from 'playwright';
-import { startInstalledApp } from '../installed-utils';
-import { TestUtils } from '../../utils';
import { colors } from '../../../../src/config.json';
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { TestUtils } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
const SHADOWSOCKS_PORT = 65_000;
const UDPOVERTCP_PORT = '80';
@@ -25,14 +25,14 @@ test.afterAll(async () => {
});
test('App should have automatic obfuscation', async () => {
- await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
- expect(
- await util.waitForNavigation(async () => await page.getByText('VPN settings').click()),
- ).toBe(RoutePath.vpnSettings);
+ await util.waitForNavigation(() => page.click('button[aria-label="Settings"]'));
+ expect(await util.waitForNavigation(() => page.getByText('VPN settings').click())).toBe(
+ RoutePath.vpnSettings,
+ );
- expect(
- await util.waitForNavigation(async () => await page.getByText('WireGuard settings').click()),
- ).toBe(RoutePath.wireguardSettings);
+ expect(await util.waitForNavigation(() => page.getByText('WireGuard settings').click())).toBe(
+ RoutePath.wireguardSettings,
+ );
const automatic = page.getByTestId('automatic-obfuscation');
await expect(automatic).toHaveCSS('background-color', colors.green);
@@ -45,9 +45,7 @@ test('App should have automatic obfuscation', async () => {
test('App should set obfuscation to shadowsocks with custom port', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="Shadowsocks settings"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="Shadowsocks settings"]')),
).toBe(RoutePath.shadowsocks);
const automatic = page.locator('button', { hasText: 'Automatic' });
@@ -61,7 +59,7 @@ test('App should set obfuscation to shadowsocks with custom port', async () => {
const customItem = page.locator('div[role="option"]', { hasText: 'Custom' });
await expect(customItem).toHaveCSS('background-color', colors.green);
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'));
+ await util.waitForNavigation(() => page.click('button[aria-label="Back"]'));
const shadowsocksItem = page.locator('button', { hasText: 'Shadowsocks' });
await shadowsocksItem.click();
@@ -74,22 +72,18 @@ test('App should set obfuscation to shadowsocks with custom port', async () => {
test('App should still have shadowsocks custom port', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="Shadowsocks settings"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="Shadowsocks settings"]')),
).toBe(RoutePath.shadowsocks);
const customItem = page.locator('div[role="option"]', { hasText: 'Custom' });
await expect(customItem).toHaveCSS('background-color', colors.green);
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'));
+ await util.waitForNavigation(() => page.click('button[aria-label="Back"]'));
});
test('App should set obfuscation to UDP-over-TCP with port', async () => {
expect(
- await util.waitForNavigation(
- async () => await page.click('button[aria-label="UDP-over-TCP settings"]'),
- ),
+ await util.waitForNavigation(() => page.click('button[aria-label="UDP-over-TCP settings"]')),
).toBe(RoutePath.udpOverTcp);
const automatic = page.locator('button', { hasText: 'Automatic' });
@@ -100,7 +94,7 @@ test('App should set obfuscation to UDP-over-TCP with port', async () => {
await expect(portButton).toHaveCSS('background-color', colors.green);
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'));
+ await util.waitForNavigation(() => page.click('button[aria-label="Back"]'));
const udpOverTcpItem = page.locator('button', { hasText: 'UDP-over-TCP' });
await udpOverTcpItem.click();
diff --git a/gui/test/e2e/installed/state-dependent/settings-import.spec.ts b/gui/test/e2e/installed/state-dependent/settings-import.spec.ts
index 6b37b36244..d9a8859f76 100644
--- a/gui/test/e2e/installed/state-dependent/settings-import.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/settings-import.spec.ts
@@ -1,9 +1,9 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { startInstalledApp } from '../installed-utils';
-import { TestUtils } from '../../utils';
import { RoutePath } from '../../../../src/renderer/lib/routes';
+import { TestUtils } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
const INVALID_JSON = 'invalid json';
const VALID_JSON = `
@@ -31,14 +31,14 @@ test.afterAll(async () => {
});
async function navigateToSettingsImport() {
- await util.waitForNavigation(async () => await page.click('button[aria-label="Settings"]'));
- await util.waitForNavigation(async () => await page.getByText('VPN settings').click());
+ await util.waitForNavigation(() => page.click('button[aria-label="Settings"]'));
+ await util.waitForNavigation(() => page.getByText('VPN settings').click());
- expect(
- await util.waitForNavigation(async () => await page.getByText('Server IP override').click())
- ).toEqual(RoutePath.settingsImport);
+ expect(await util.waitForNavigation(() => page.getByText('Server IP override').click())).toEqual(
+ RoutePath.settingsImport,
+ );
- const title = page.locator('h1')
+ const title = page.locator('h1');
await expect(title).toHaveText('Server IP override');
}
@@ -49,14 +49,14 @@ test('App should display no overrides', async () => {
});
test('App should fail to import text', async () => {
- expect(
- await util.waitForNavigation(async () => await page.getByText('Import via text').click())
- ).toEqual(RoutePath.settingsTextImport);
+ expect(await util.waitForNavigation(() => page.getByText('Import via text').click())).toEqual(
+ RoutePath.settingsTextImport,
+ );
await page.locator('textarea').fill(INVALID_JSON);
- expect(
- await util.waitForNavigation(async () => await page.click('button[aria-label="Save"]'))
- ).toEqual(RoutePath.settingsImport);
+ expect(await util.waitForNavigation(() => page.click('button[aria-label="Save"]'))).toEqual(
+ RoutePath.settingsImport,
+ );
await expect(page.getByTestId('status-title')).toHaveText('NO OVERRIDES IMPORTED');
await expect(page.getByTestId('status-subtitle')).toBeVisible();
@@ -65,16 +65,16 @@ test('App should fail to import text', async () => {
});
test('App should succeed to import text', async () => {
- expect(
- await util.waitForNavigation(async () => await page.getByText('Import via text').click())
- ).toEqual(RoutePath.settingsTextImport);
+ expect(await util.waitForNavigation(() => page.getByText('Import via text').click())).toEqual(
+ RoutePath.settingsTextImport,
+ );
const textarea = page.locator('textarea');
await expect(textarea).toHaveValue(INVALID_JSON);
await textarea.fill(VALID_JSON);
- expect(
- await util.waitForNavigation(async () => await page.click('button[aria-label="Save"]'))
- ).toEqual(RoutePath.settingsImport);
+ expect(await util.waitForNavigation(() => page.click('button[aria-label="Save"]'))).toEqual(
+ RoutePath.settingsImport,
+ );
await expect(page.getByTestId('status-title')).toHaveText('IMPORT SUCCESSFUL');
await expect(page.getByTestId('status-subtitle')).toBeVisible();
@@ -83,24 +83,24 @@ test('App should succeed to import text', async () => {
await expect(page.getByTestId('status-title')).toHaveText('OVERRIDES ACTIVE');
- expect(
- await util.waitForNavigation(async () => await page.getByText('Import via text').click())
- ).toEqual(RoutePath.settingsTextImport);
+ expect(await util.waitForNavigation(() => page.getByText('Import via text').click())).toEqual(
+ RoutePath.settingsTextImport,
+ );
await expect(textarea).toHaveValue('');
- expect(
- await util.waitForNavigation(async () => await page.click('button[aria-label="Close"]'))
- ).toEqual(RoutePath.settingsImport);
+ expect(await util.waitForNavigation(() => page.click('button[aria-label="Close"]'))).toEqual(
+ RoutePath.settingsImport,
+ );
});
test('App should show active overrides', async () => {
- expect(
- await util.waitForNavigation(async () => await page.click('button[aria-label="Back"]'))
- ).toEqual(RoutePath.vpnSettings);
- expect(
- await util.waitForNavigation(async () => await page.getByText('Server IP override').click())
- ).toEqual(RoutePath.settingsImport);
+ expect(await util.waitForNavigation(() => page.click('button[aria-label="Back"]'))).toEqual(
+ RoutePath.vpnSettings,
+ );
+ expect(await util.waitForNavigation(() => page.getByText('Server IP override').click())).toEqual(
+ RoutePath.settingsImport,
+ );
await expect(page.getByTestId('status-title')).toHaveText('OVERRIDES ACTIVE');
await expect(page.getByText('Clear all overrides')).toBeEnabled();
diff --git a/gui/test/e2e/installed/state-dependent/too-many-devices.spec.ts b/gui/test/e2e/installed/state-dependent/too-many-devices.spec.ts
index 9b6dbdbe3d..8ff8675b67 100644
--- a/gui/test/e2e/installed/state-dependent/too-many-devices.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/too-many-devices.spec.ts
@@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { Locator, Page } from 'playwright';
+
import { RoutePath } from '../../../../src/renderer/lib/routes';
import { TestUtils } from '../../utils';
-
import { startInstalledApp } from '../installed-utils';
// This test expects the daemon to be logged out and the provided account to have five registered
@@ -25,25 +25,26 @@ test('App should show too many devices', async () => {
expect(await util.currentRoute()).toEqual(RoutePath.login);
const loginInput = getInput(page);
- await loginInput.type(process.env.ACCOUNT_NUMBER!);
+ await loginInput.fill(process.env.ACCOUNT_NUMBER!);
- expect(await util.waitForNavigation(() => {
- loginInput.press('Enter');
- })).toEqual(RoutePath.tooManyDevices);
+ expect(await util.waitForNavigation(() => loginInput.press('Enter'))).toEqual(
+ RoutePath.tooManyDevices,
+ );
const loginButton = page.getByText('Continue with login');
await expect(page.getByTestId('title')).toHaveText('Too many devices');
await expect(loginButton).toBeDisabled();
- await page.getByLabel(/^Remove device named/).first().click();
+ await page
+ .getByLabel(/^Remove device named/)
+ .first()
+ .click();
await page.getByText('Yes, log out device').click();
await expect(loginButton).toBeEnabled();
// Trigger transition: too-many-devices -> login -> main
- expect(await util.waitForNavigation(() => {
- loginButton.click();
- })).toEqual(RoutePath.login);
+ expect(await util.waitForNavigation(() => loginButton.click())).toEqual(RoutePath.login);
// Note: `util.waitForNavigation` won't return the navigation event when
// transitioning from login -> main, so we need to observe the state of the
diff --git a/gui/test/e2e/installed/state-dependent/tunnel-state.spec.ts b/gui/test/e2e/installed/state-dependent/tunnel-state.spec.ts
index 53332ee6ab..15ce240e57 100644
--- a/gui/test/e2e/installed/state-dependent/tunnel-state.spec.ts
+++ b/gui/test/e2e/installed/state-dependent/tunnel-state.spec.ts
@@ -1,15 +1,11 @@
-import { exec as execAsync } from 'child_process';
-import { promisify } from 'util';
import { expect, test } from '@playwright/test';
+import { exec as execAsync } from 'child_process';
import { Page } from 'playwright';
-import {
- expectConnected,
- expectDisconnected,
- expectError,
-} from '../../shared/tunnel-state';
+import { promisify } from 'util';
-import { startInstalledApp } from '../installed-utils';
+import { expectConnected, expectDisconnected, expectError } from '../../shared/tunnel-state';
import { escapeRegExp } from '../../utils';
+import { startInstalledApp } from '../installed-utils';
const exec = promisify(execAsync);
@@ -164,7 +160,9 @@ test('App should show multihop', async () => {
await exec('mullvad relay set tunnel wireguard --use-multihop=on');
await expectConnected(page);
const relay = page.getByTestId('hostname-line');
- await expect(relay).toHaveText(new RegExp('^' + escapeRegExp(`${process.env.HOSTNAME} via`), 'i'));
+ await expect(relay).toHaveText(
+ new RegExp('^' + escapeRegExp(`${process.env.HOSTNAME} via`), 'i'),
+ );
await exec('mullvad relay set tunnel wireguard --use-multihop=off');
await page.getByText('Disconnect').click();
});
diff --git a/gui/test/e2e/mocked/expired-account-error-view.spec.ts b/gui/test/e2e/mocked/expired-account-error-view.spec.ts
index 5c906a134b..e63e62f51f 100644
--- a/gui/test/e2e/mocked/expired-account-error-view.spec.ts
+++ b/gui/test/e2e/mocked/expired-account-error-view.spec.ts
@@ -1,10 +1,11 @@
-import { Page } from 'playwright';
-import { MockedTestUtils, startMockedApp } from './mocked-utils';
import { expect, test } from '@playwright/test';
-import { IAccountData } from '../../../src/shared/daemon-rpc-types';
-import { getBackgroundColor } from '../utils';
+import { Page } from 'playwright';
+
import { colors } from '../../../src/config.json';
import { RoutePath } from '../../../src/renderer/lib/routes';
+import { IAccountData } from '../../../src/shared/daemon-rpc-types';
+import { getBackgroundColor } from '../utils';
+import { MockedTestUtils, startMockedApp } from './mocked-utils';
let page: Page;
let util: MockedTestUtils;
@@ -37,10 +38,12 @@ test('App should show out of time view after running out of time', async () => {
const expiryDate = new Date();
expiryDate.setSeconds(expiryDate.getSeconds() + 2);
- expect(await util.waitForNavigation(async () => {
- await util.sendMockIpcResponse<IAccountData>({
- channel: 'account-',
- response: { expiry: expiryDate.toISOString() },
- });
- })).toEqual(RoutePath.expired);
+ expect(
+ await util.waitForNavigation(async () => {
+ await util.sendMockIpcResponse<IAccountData>({
+ channel: 'account-',
+ response: { expiry: expiryDate.toISOString() },
+ });
+ }),
+ ).toEqual(RoutePath.expired);
});
diff --git a/gui/test/e2e/mocked/feature-indicators.spec.ts b/gui/test/e2e/mocked/feature-indicators.spec.ts
index 1ba216bd52..6e0e034c35 100644
--- a/gui/test/e2e/mocked/feature-indicators.spec.ts
+++ b/gui/test/e2e/mocked/feature-indicators.spec.ts
@@ -1,9 +1,14 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { MockedTestUtils, startMockedApp } from './mocked-utils';
-import { FeatureIndicator, ILocation, ITunnelEndpoint, TunnelState } from '../../../src/shared/daemon-rpc-types';
+import {
+ FeatureIndicator,
+ ILocation,
+ ITunnelEndpoint,
+ TunnelState,
+} from '../../../src/shared/daemon-rpc-types';
import { expectConnected } from '../shared/tunnel-state';
+import { MockedTestUtils, startMockedApp } from './mocked-utils';
const endpoint: ITunnelEndpoint = {
address: 'wg10:80',
@@ -91,23 +96,23 @@ test('App should show feature indicators', async () => {
await expect(ellipsis).toBeVisible();
await expectConnected(page);
- await expectFeatureIndicators(page, ["DAITA", "Quantum resistance"], false);
- await expectHiddenFeatureIndicator(page, "Mssfix");
+ await expectFeatureIndicators(page, ['DAITA', 'Quantum resistance'], false);
+ await expectHiddenFeatureIndicator(page, 'Mssfix');
await page.getByTestId('connection-panel-chevron').click();
await expect(ellipsis).not.toBeVisible();
await expectFeatureIndicators(page, [
- "DAITA",
- "Quantum resistance",
- "Mssfix",
- "MTU",
- "Obfuscation",
- "Local network sharing",
- "Lockdown mode",
- "Multihop",
- "Custom DNS",
- "Server IP override",
+ 'DAITA',
+ 'Quantum resistance',
+ 'Mssfix',
+ 'MTU',
+ 'Obfuscation',
+ 'Local network sharing',
+ 'Lockdown mode',
+ 'Multihop',
+ 'Custom DNS',
+ 'Server IP override',
]);
});
@@ -123,11 +128,7 @@ async function expectHiddenFeatureIndicator(page: Page, hiddenIndicator: string)
await expect(indicator).not.toBeVisible();
}
-async function expectFeatureIndicators(
- page: Page,
- expectedIndicators: Array<string>,
- only = true,
-) {
+async function expectFeatureIndicators(page: Page, expectedIndicators: Array<string>, only = true) {
const indicators = page.getByTestId('feature-indicator');
if (only) {
await expect(indicators).toHaveCount(expectedIndicators.length);
diff --git a/gui/test/e2e/mocked/mocked-utils.ts b/gui/test/e2e/mocked/mocked-utils.ts
index 1840ebbbb0..57998fae68 100644
--- a/gui/test/e2e/mocked/mocked-utils.ts
+++ b/gui/test/e2e/mocked/mocked-utils.ts
@@ -3,7 +3,7 @@ import { ElectronApplication } from 'playwright';
import { startApp, TestUtils } from '../utils';
interface StartMockedAppResponse extends Awaited<ReturnType<typeof startApp>> {
- util: MockedTestUtils,
+ util: MockedTestUtils;
}
export interface MockedTestUtils extends TestUtils {
@@ -22,7 +22,7 @@ export const startMockedApp = async (): Promise<StartMockedAppResponse> => {
...startAppResult.util,
mockIpcHandle,
sendMockIpcResponse,
- }
+ },
};
};
diff --git a/gui/test/e2e/mocked/settings.spec.ts b/gui/test/e2e/mocked/settings.spec.ts
index c6022678f5..52dbc72402 100644
--- a/gui/test/e2e/mocked/settings.spec.ts
+++ b/gui/test/e2e/mocked/settings.spec.ts
@@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { Page } from 'playwright';
-import { MockedTestUtils, startMockedApp } from './mocked-utils';
import { IAccountData } from '../../../src/shared/daemon-rpc-types';
+import { MockedTestUtils, startMockedApp } from './mocked-utils';
let page: Page;
let util: MockedTestUtils;
@@ -21,7 +21,7 @@ test('Account button should be displayed correctly', async () => {
});
test('Headerbar account info should be displayed correctly', async () => {
- let expiryText = page.getByText(/^Time left:/);
+ const expiryText = page.getByText(/^Time left:/);
await expect(expiryText).toContainText(/Time left: 29 days/i);
/**
diff --git a/gui/test/e2e/mocked/tunnel-state.spec.ts b/gui/test/e2e/mocked/tunnel-state.spec.ts
index 3e20dab6ec..6ecf707ba6 100644
--- a/gui/test/e2e/mocked/tunnel-state.spec.ts
+++ b/gui/test/e2e/mocked/tunnel-state.spec.ts
@@ -1,9 +1,20 @@
import { test } from '@playwright/test';
import { Page } from 'playwright';
+import {
+ ErrorStateCause,
+ ILocation,
+ ITunnelEndpoint,
+ TunnelState,
+} from '../../../src/shared/daemon-rpc-types';
+import {
+ expectConnected,
+ expectConnecting,
+ expectDisconnected,
+ expectDisconnecting,
+ expectError,
+} from '../shared/tunnel-state';
import { MockedTestUtils, startMockedApp } from './mocked-utils';
-import { ErrorStateCause, ILocation, ITunnelEndpoint, TunnelState } from '../../../src/shared/daemon-rpc-types';
-import { expectConnected, expectConnecting, expectDisconnected, expectDisconnecting, expectError } from '../shared/tunnel-state';
const mockLocation: ILocation = {
country: 'Sweden',
diff --git a/gui/test/e2e/shared/tunnel-state.ts b/gui/test/e2e/shared/tunnel-state.ts
index 4d75f79d01..6a5fbfaf09 100644
--- a/gui/test/e2e/shared/tunnel-state.ts
+++ b/gui/test/e2e/shared/tunnel-state.ts
@@ -1,5 +1,6 @@
import { expect } from '@playwright/test';
import { Page } from 'playwright';
+
import { colors } from '../../../src/config.json';
import { anyOf } from '../utils';
diff --git a/gui/test/e2e/utils.ts b/gui/test/e2e/utils.ts
index 082ccfcf3f..2fb33319f2 100644
--- a/gui/test/e2e/utils.ts
+++ b/gui/test/e2e/utils.ts
@@ -1,4 +1,4 @@
-import { Locator, Page, _electron as electron, ElectronApplication } from 'playwright';
+import { _electron as electron, ElectronApplication, Locator, Page } from 'playwright';
export interface StartAppResponse {
app: ElectronApplication;
@@ -8,7 +8,7 @@ export interface StartAppResponse {
export interface TestUtils {
currentRoute: () => Promise<string>;
- waitForNavigation: (initiateNavigation?: () => Promise<void> | void) => Promise<string>;
+ waitForNavigation: (initiateNavigation?: () => Promise<void> | void) => Promise<string>;
waitForNoTransition: () => Promise<void>;
}
@@ -38,34 +38,28 @@ export const startApp = async (options: LaunchOptions): Promise<StartAppResponse
return { app, page, util };
};
-export const launch = async (options: LaunchOptions): Promise<ElectronApplication> => {
+export const launch = (options: LaunchOptions): Promise<ElectronApplication> => {
process.env.CI = 'e2e';
- return await electron.launch(options);
-}
+ return electron.launch(options);
+};
const currentRouteFactory = (app: ElectronApplication) => {
- return async () => {
- return await app.evaluate<string>(({ webContents }) => {
- return webContents.getAllWebContents()
- // Select window that isn't devtools
- .find((webContents) => webContents.getURL().startsWith('file://'))!
- .executeJavaScript('window.e2e.location');
- });
- };
-}
+ return () =>
+ app.evaluate<string>(({ webContents }) =>
+ webContents
+ .getAllWebContents()
+ // Select window that isn't devtools
+ .find((webContents) => webContents.getURL().startsWith('file://'))!
+ .executeJavaScript('window.e2e.location'),
+ );
+};
-const waitForNavigationFactory = (
- app: ElectronApplication,
- page: Page,
-) => {
+const waitForNavigationFactory = (app: ElectronApplication, page: Page) => {
// 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(app),
- initiateNavigation?.(),
- ]);
+ const [route] = await Promise.all([waitForNextRoute(app), initiateNavigation?.()]);
// Wait for view corresponding to new route to appear
await page.getByTestId(route).isVisible();
@@ -77,7 +71,7 @@ const waitForNavigationFactory = (
const waitForNoTransition = async (page: Page) => {
// Wait until there's only one transitionContents
- let transitionContentsCount;
+ let transitionContentsCount;
do {
if (transitionContentsCount !== undefined) {
await new Promise((resolve) => setTimeout(resolve, 5));
@@ -93,14 +87,15 @@ const waitForNoTransition = async (page: Page) => {
};
// Returns the route when it changes
-const waitForNextRoute = async (app: ElectronApplication): Promise<string> => {
- return await app.evaluate(({ ipcMain }) => {
- return new Promise((resolve) => {
- ipcMain.once('navigation-setHistory', (_event, history: History) => {
+const waitForNextRoute = (app: ElectronApplication): Promise<string> => {
+ return app.evaluate(
+ ({ ipcMain }) =>
+ new Promise((resolve) => {
+ ipcMain.once('navigation-setHistory', (_event, history: History) => {
resolve(history.entries[history.index].pathname);
- });
- });
- });
+ });
+ }),
+ );
};
const getStyleProperty = (locator: Locator, property: string) => {
@@ -125,5 +120,5 @@ export function anyOf(...values: string[]): RegExp {
}
export function escapeRegExp(regexp: string): string {
- return regexp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+ return regexp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}