Linter and more test fixes
|
@ -1,4 +1,4 @@
|
|||
name: Relay e2e Tests
|
||||
name: Relay e2e tests
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
@ -17,7 +17,7 @@ on:
|
|||
|
||||
jobs:
|
||||
relaye2e:
|
||||
name: 'Relay Application e2e Tests'
|
||||
name: 'Relay e2e all tests'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -53,3 +53,4 @@ jobs:
|
|||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 5
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
name: Relay e2e health check
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Environment to run the e2e against'
|
||||
required: true
|
||||
default: 'stage'
|
||||
type: choice
|
||||
options:
|
||||
- stage
|
||||
- prod
|
||||
- dev
|
||||
push:
|
||||
branches:
|
||||
- "e2e-tests-enhancement-work"
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- ".github/workflows/relay_e2e_health.yml"
|
||||
|
||||
jobs:
|
||||
relay_health_check:
|
||||
name: 'Relay e2e health check'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: 'frontend/package.json'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: |
|
||||
npm install -D @playwright/test --with-deps
|
||||
npx playwright install
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
commandenv="${{ inputs.environment != null && inputs.environment || 'stage' }}"
|
||||
E2E_TEST_ENV=$commandenv npx playwright test --grep "@health_check"
|
||||
env:
|
||||
E2E_TEST_ENV: ${{ inputs.environment != null && inputs.environment || 'stage' }}
|
||||
E2E_TEST_ACCOUNT_FREE: ${{ secrets.E2E_TEST_ACCOUNT_FREE }}
|
||||
E2E_TEST_ACCOUNT_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_PASSWORD }}
|
||||
E2E_TEST_ACCOUNT_PREMIUM: ${{ secrets.E2E_TEST_ACCOUNT_PREMIUM }}
|
||||
E2E_TEST_BASE_URL: ${{ secrets.E2E_TEST_BASE_URL }}
|
||||
|
||||
- name: Upload html report as artifact to troubleshoot failures.
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 5
|
|
@ -32,20 +32,49 @@ npx playwright install
|
|||
|
||||
### 5. Run Tests
|
||||
|
||||
If you are running only free specs, this following will suffice.
|
||||
|
||||
```
|
||||
create/update a .env file with the following:
|
||||
|
||||
E2E_TEST_ACCOUNT_PASSWORD=<arbitrary password>
|
||||
```
|
||||
|
||||
If you are running premium tests as well, you will need the following.
|
||||
|
||||
```
|
||||
create/update a .env file with the following:
|
||||
|
||||
E2E_TEST_ACCOUNT_PREMIUM = <your_premium_account_email>
|
||||
E2E_TEST_ACCOUNT_PASSWORD=<your_premium_account_password>
|
||||
```
|
||||
|
||||
Any free account created during the initial setup of tests will also use `E2E_TEST_ACCOUNT_PASSWORD`. If you do not want to use a personal premium account, reach out to Luke for `relay-team` premium account details.
|
||||
|
||||
### 6. Run Tests
|
||||
|
||||
```
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
By default, `npm run test:e2e` will run the tests on https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net/.
|
||||
By default, `npm run test:e2e` will run the tests on https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net/.
|
||||
|
||||
You can also run tests locally, on our dev server (https://dev.fxprivaterelay.nonprod.cloudops.mozgcp.net/), and in production (https://relay.firefox.com/). You can find the commands [here](https://github.com/mozilla/fx-private-relay/blob/main/package.json#L26-L31). To view the tests live in the browser, you can add `--headed` to the end of the command. See https://playwright.dev/docs/test-cli for more flags.
|
||||
You can also run tests locally, on our dev server (https://dev.fxprivaterelay.nonprod.cloudops.mozgcp.net/), and in production (https://relay.firefox.com/). You can find the commands [here](https://github.com/mozilla/fx-private-relay/blob/main/package.json#L26-L31), or you can run `E2E_TEST_ENV=<env (prod, dev, stage)> npx playwright test`. To view the tests live in the browser, you can add `--headed` to the end of the command. See https://playwright.dev/docs/test-cli for more flags.
|
||||
|
||||
[![Relay e2e Tests](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml/badge.svg)](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml)
|
||||
Our github actions workflows can be found here, [![Relay e2e Tests](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml/badge.svg)](https://github.com/mozilla/fx-private-relay/actions/workflows/playwright.yml). You can run the tests against different branches.
|
||||
|
||||
### 7. Screenshots
|
||||
|
||||
When you run tests for the first time locally, you will run into an error for the tests that rely on screenshots, stating that a snapshot does not exist. Here is an example.
|
||||
|
||||
```
|
||||
Error: A snapshot doesn't exist at example.spec.ts-snapshots/example-test-1-chromium-darwin.png, writing actual.
|
||||
```
|
||||
|
||||
This is because playwright needs to create an image initially. On the following runs, it will compare that a screenshot of the respective element matches the one added initially. Do not push your local images into the repo, the only ones that are needed for CI end in `linux`.
|
||||
|
||||
### 8. Linting
|
||||
|
||||
To lint the files, run the following in the root directory (it is recommended to run this after any changes to the test suite):
|
||||
|
||||
`npx prettier --write e2e-tests/*`
|
||||
|
|
|
@ -1,95 +1,114 @@
|
|||
import { APIRequestContext, Page, request } from '@playwright/test';
|
||||
import { APIRequestContext, Page, request } from "@playwright/test";
|
||||
|
||||
export const ENV_EMAIL_DOMAINS = {
|
||||
stage: '@mozmail.fxprivaterelay.nonprod.cloudops.mozgcp.net',
|
||||
prod: '@mozmail.com',
|
||||
dev: '@mozmail.dev.fxprivaterelay.nonprod.cloudops.mozgcp.net',
|
||||
local: '@mozmail.com'
|
||||
}
|
||||
stage: "@mozmail.fxprivaterelay.nonprod.cloudops.mozgcp.net",
|
||||
prod: "@mozmail.com",
|
||||
dev: "@mozmail.dev.fxprivaterelay.nonprod.cloudops.mozgcp.net",
|
||||
local: "@mozmail.com",
|
||||
};
|
||||
|
||||
export const ENV_URLS = {
|
||||
stage: 'https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net',
|
||||
prod: 'https://relay.firefox.com',
|
||||
dev: 'https://dev.fxprivaterelay.nonprod.cloudops.mozgcp.net',
|
||||
local: process.env.SITE_ORIGIN
|
||||
}
|
||||
stage: "https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net",
|
||||
prod: "https://relay.firefox.com",
|
||||
dev: "https://dev.fxprivaterelay.nonprod.cloudops.mozgcp.net",
|
||||
local: process.env.SITE_ORIGIN,
|
||||
};
|
||||
|
||||
export const getVerificationCode = async (testEmail: string, page: Page, attempts = 10) => {
|
||||
export const getVerificationCode = async (
|
||||
testEmail: string,
|
||||
page: Page,
|
||||
attempts = 10,
|
||||
) => {
|
||||
if (attempts === 0) {
|
||||
throw new Error('Unable to retrieve restmail data');
|
||||
throw new Error("Unable to retrieve restmail data");
|
||||
}
|
||||
|
||||
const context = await request.newContext();
|
||||
const res = await context.get(
|
||||
`http://restmail.net/mail/${testEmail}`,
|
||||
{
|
||||
failOnStatusCode: false
|
||||
}
|
||||
);
|
||||
const res = await context.get(`http://restmail.net/mail/${testEmail}`, {
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
const resJson = await res.json();
|
||||
if (resJson.length) {
|
||||
const verificationCode = resJson[0].headers['x-verify-short-code']
|
||||
const verificationCode = resJson[0].headers["x-verify-short-code"];
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForTimeout(2000);
|
||||
return getVerificationCode(testEmail, page, attempts - 1);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteEmailAddressMessages = async (req: APIRequestContext, testEmail: string) => {
|
||||
export const deleteEmailAddressMessages = async (
|
||||
req: APIRequestContext,
|
||||
testEmail: string,
|
||||
) => {
|
||||
try {
|
||||
await req.delete(`http://restmail.net/mail/${testEmail}`);
|
||||
} catch (err) {
|
||||
console.error('ERROR DELETE RESTMAIL EMAIL', err);
|
||||
console.error("ERROR DELETE RESTMAIL EMAIL", err);
|
||||
}
|
||||
};
|
||||
|
||||
const setYourPassword = async (page: Page) => {
|
||||
await page.locator('#password').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string)
|
||||
await page.locator('#vpassword').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string)
|
||||
await page.locator('#age').fill('31');
|
||||
await page.locator('button:has-text("Create account")').click({force: true})
|
||||
await page.waitForTimeout(500)
|
||||
await checkAuthState(page)
|
||||
}
|
||||
await page
|
||||
.locator("#password")
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await page
|
||||
.locator("#vpassword")
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await page.locator("#age").fill("31");
|
||||
await page
|
||||
.locator('button:has-text("Create account")')
|
||||
.click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
await checkAuthState(page);
|
||||
};
|
||||
|
||||
const enterConfirmationCode = async (page: Page) => {
|
||||
const maybeVerificationCodeInput = 'div.card input'
|
||||
await page.waitForSelector(maybeVerificationCodeInput, { timeout: 2000 })
|
||||
const confirmButton = page.locator('#submit-btn')
|
||||
const verificationCode = await getVerificationCode(process.env.E2E_TEST_ACCOUNT_FREE as string, page)
|
||||
await page.locator(maybeVerificationCodeInput).fill(verificationCode)
|
||||
await confirmButton.click({force: true})
|
||||
await page.waitForTimeout(500)
|
||||
await checkAuthState(page)
|
||||
}
|
||||
const maybeVerificationCodeInput = "div.card input";
|
||||
await page.waitForSelector(maybeVerificationCodeInput, { timeout: 2000 });
|
||||
const confirmButton = page.locator("#submit-btn");
|
||||
const verificationCode = await getVerificationCode(
|
||||
process.env.E2E_TEST_ACCOUNT_FREE as string,
|
||||
page,
|
||||
);
|
||||
await page.locator(maybeVerificationCodeInput).fill(verificationCode);
|
||||
await confirmButton.click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
await checkAuthState(page);
|
||||
};
|
||||
|
||||
const signIn = async (page: Page) => {
|
||||
const signInButton = '//button[@id="use-logged-in"]'
|
||||
await page.waitForSelector(signInButton, { timeout: 2000 })
|
||||
await page.locator(signInButton).click({force: true})
|
||||
await page.waitForTimeout(500)
|
||||
await checkAuthState(page)
|
||||
}
|
||||
const signInButton = page.getByRole("button", { name: "Sign in" });
|
||||
await signInButton.waitFor({ timeout: 2000 });
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.getByRole("button", { name: "Sign in" }).click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
await checkAuthState(page);
|
||||
};
|
||||
|
||||
const enterYourEmail = async (page: Page) => {
|
||||
const maybeEmailInput = 'input[name="email"]'
|
||||
await page.waitForSelector(maybeEmailInput, { timeout: 2000 })
|
||||
const signInButton = page.locator('#submit-btn')
|
||||
await page.locator(maybeEmailInput).fill(process.env.E2E_TEST_ACCOUNT_FREE as string)
|
||||
await signInButton.click({force: true})
|
||||
await page.waitForTimeout(500)
|
||||
await checkAuthState(page)
|
||||
}
|
||||
const maybeEmailInput = 'input[name="email"]';
|
||||
await page.waitForSelector(maybeEmailInput, { timeout: 2000 });
|
||||
const signInButton = page.locator("#submit-btn");
|
||||
await page
|
||||
.locator(maybeEmailInput)
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_FREE as string);
|
||||
await signInButton.click({ force: true });
|
||||
await page.waitForTimeout(500);
|
||||
await checkAuthState(page);
|
||||
};
|
||||
|
||||
const enterYourPassword = async (page: Page) => {
|
||||
await page.locator('#password').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string)
|
||||
await page
|
||||
.locator("#password")
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
|
||||
// using force here due to fxa issue with playwright
|
||||
await page.locator('#submit-btn').click()
|
||||
await page.waitForTimeout(500)
|
||||
await checkAuthState(page)
|
||||
}
|
||||
await page.locator("#submit-btn").click();
|
||||
await page.waitForTimeout(500);
|
||||
await checkAuthState(page);
|
||||
};
|
||||
|
||||
export const generateRandomEmail = async () => {
|
||||
return `${Date.now()}_tstact@restmail.net`;
|
||||
|
@ -98,46 +117,68 @@ export const generateRandomEmail = async () => {
|
|||
export const setEnvVariables = async (email: string) => {
|
||||
// set env variables
|
||||
// stage will currently be the default
|
||||
process.env['E2E_TEST_ENV'] = process.env.E2E_TEST_ENV as string ?? 'stage';
|
||||
process.env['E2E_TEST_ACCOUNT_FREE'] = email;
|
||||
process.env['E2E_TEST_BASE_URL'] = ENV_URLS[process.env.E2E_TEST_ENV as string] ?? ENV_URLS.stage
|
||||
}
|
||||
process.env["E2E_TEST_ENV"] = (process.env.E2E_TEST_ENV as string) ?? "stage";
|
||||
process.env["E2E_TEST_ACCOUNT_FREE"] = email;
|
||||
process.env["E2E_TEST_BASE_URL"] =
|
||||
ENV_URLS[process.env.E2E_TEST_ENV as string] ?? ENV_URLS.stage;
|
||||
};
|
||||
|
||||
interface DefaultScreenshotOpts {
|
||||
animations?: "disabled" | "allow" | undefined
|
||||
animations?: "disabled" | "allow" | undefined;
|
||||
maxDiffPixelRatio?: number | undefined;
|
||||
}
|
||||
|
||||
export const defaultScreenshotOpts: Partial<DefaultScreenshotOpts> = {
|
||||
animations: 'disabled',
|
||||
maxDiffPixelRatio: 0.04
|
||||
animations: "disabled",
|
||||
maxDiffPixelRatio: 0.04,
|
||||
};
|
||||
|
||||
export const forceNonReactLink = async (page: Page) => {
|
||||
/**
|
||||
* There is a small chance you are redirected to an FxA auth page with the parameter showReactApp=true.
|
||||
* This causes the page to look different, and our selectors for the auth page to become flaky because id's are missing.
|
||||
*/
|
||||
const url = new URL(page.url());
|
||||
if (url.searchParams.get('showReactApp') === 'true') {
|
||||
url.searchParams.set('showReactApp', 'false');
|
||||
await page.goto(url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export const checkAuthState = async (page: Page) => {
|
||||
try {
|
||||
const authStateTitleString = await page.locator('h1').first()?.textContent({ timeout: 4000 })
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
const authStateTitleString = await page
|
||||
.locator("h1")
|
||||
.first()
|
||||
?.textContent({ timeout: 5000 });
|
||||
|
||||
const checkIfTitleContains = (potentialTitle: string) => {
|
||||
return authStateTitleString?.includes(potentialTitle)
|
||||
}
|
||||
return authStateTitleString?.includes(potentialTitle);
|
||||
};
|
||||
|
||||
await forceNonReactLink(page);
|
||||
|
||||
switch (true) {
|
||||
case checkIfTitleContains('Enter your email'):
|
||||
await enterYourEmail(page)
|
||||
case checkIfTitleContains("Enter your email"):
|
||||
await enterYourEmail(page);
|
||||
break;
|
||||
case checkIfTitleContains('Enter your password'):
|
||||
await enterYourPassword(page)
|
||||
case checkIfTitleContains("Enter your password"):
|
||||
await enterYourPassword(page);
|
||||
break;
|
||||
case checkIfTitleContains('Set your password'):
|
||||
await setYourPassword(page)
|
||||
case checkIfTitleContains("Set your password"):
|
||||
await setYourPassword(page);
|
||||
break;
|
||||
case checkIfTitleContains('Enter confirmation code'):
|
||||
await enterConfirmationCode(page)
|
||||
case checkIfTitleContains("Enter confirmation code"):
|
||||
await enterConfirmationCode(page);
|
||||
break;
|
||||
case checkIfTitleContains('Sign in'):
|
||||
await signIn(page)
|
||||
case checkIfTitleContains("Sign in"):
|
||||
await signIn(page);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
import { LandingPage } from "../pages/landingPage";
|
||||
import { AuthPage } from "../pages/authPage";
|
||||
import { test as baseTest } from '@playwright/test'
|
||||
import { test as baseTest } from "@playwright/test";
|
||||
import { DashboardPage } from "../pages/dashboardPage";
|
||||
import { SubscriptionPaymentPage } from "../pages/subscriptionPaymentPage";
|
||||
import { MozillaMonitorPage } from "../pages/mozillaMonitorPage";
|
||||
|
||||
const test = baseTest.extend<{
|
||||
landingPage: LandingPage;
|
||||
authPage: AuthPage;
|
||||
dashboardPage: DashboardPage;
|
||||
subscriptionPage: SubscriptionPaymentPage
|
||||
landingPage: LandingPage;
|
||||
authPage: AuthPage;
|
||||
dashboardPage: DashboardPage;
|
||||
subscriptionPage: SubscriptionPaymentPage;
|
||||
mozillaMonitorPage: MozillaMonitorPage;
|
||||
}>({
|
||||
authPage: async ({ page }, use) => {
|
||||
await use(new AuthPage(page))
|
||||
},
|
||||
landingPage: async ({ page }, use) => {
|
||||
await use(new LandingPage(page))
|
||||
},
|
||||
dashboardPage: async ({ page }, use) => {
|
||||
await use(new DashboardPage(page))
|
||||
},
|
||||
subscriptionPage: async ({ page }, use) => {
|
||||
await use(new SubscriptionPaymentPage(page))
|
||||
},
|
||||
})
|
||||
authPage: async ({ page }, use) => {
|
||||
await use(new AuthPage(page));
|
||||
},
|
||||
landingPage: async ({ page }, use) => {
|
||||
await use(new LandingPage(page));
|
||||
},
|
||||
dashboardPage: async ({ page }, use) => {
|
||||
await use(new DashboardPage(page));
|
||||
},
|
||||
subscriptionPage: async ({ page }, use) => {
|
||||
await use(new SubscriptionPaymentPage(page));
|
||||
},
|
||||
mozillaMonitorPage: async ({ page }, use) => {
|
||||
await use(new MozillaMonitorPage(page));
|
||||
},
|
||||
});
|
||||
|
||||
export default test;
|
||||
export const expect = test.expect;
|
||||
export const expect = test.expect;
|
||||
|
|
|
@ -1,32 +1,36 @@
|
|||
import { ENV_URLS, getVerificationCode, setEnvVariables } from "./e2eTestUtils/helpers";
|
||||
import {
|
||||
ENV_URLS,
|
||||
getVerificationCode,
|
||||
setEnvVariables,
|
||||
} from "./e2eTestUtils/helpers";
|
||||
import { AuthPage } from "./pages/authPage";
|
||||
import { LandingPage } from "./pages/landingPage";
|
||||
|
||||
const { chromium } = require('@playwright/test');
|
||||
const { chromium } = require("@playwright/test");
|
||||
|
||||
async function globalSetup() {
|
||||
// playwright setup
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
// playwright setup
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
// generate email and set env variables
|
||||
const randomEmail = `${Date.now()}_tstact@restmail.net`
|
||||
await setEnvVariables(randomEmail)
|
||||
// generate email and set env variables
|
||||
const randomEmail = `${Date.now()}_tstact@restmail.net`;
|
||||
await setEnvVariables(randomEmail);
|
||||
|
||||
await page.goto(ENV_URLS[process.env.E2E_TEST_ENV as string])
|
||||
const landingPage = new LandingPage(page);
|
||||
await landingPage.goToSignUp()
|
||||
await page.goto(ENV_URLS[process.env.E2E_TEST_ENV as string]);
|
||||
const landingPage = new LandingPage(page);
|
||||
await landingPage.goToSignUp();
|
||||
|
||||
// register user with generated email and set as env variable
|
||||
const authPage = new AuthPage(page)
|
||||
await authPage.signUp(randomEmail)
|
||||
// register user with generated email and set as env variable
|
||||
const authPage = new AuthPage(page);
|
||||
await authPage.signUp(randomEmail);
|
||||
|
||||
// get verification code from restmail
|
||||
const verificationCode = await getVerificationCode(randomEmail, page)
|
||||
await authPage.enterVerificationCode(verificationCode)
|
||||
// get verification code from restmail
|
||||
const verificationCode = await getVerificationCode(randomEmail, page);
|
||||
await authPage.enterVerificationCode(verificationCode);
|
||||
|
||||
await page.context().storageState({ path: 'state.json' });
|
||||
await browser.close();
|
||||
await page.context().storageState({ path: "state.json" });
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
|
|
|
@ -1,57 +1,70 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
import { forceNonReactLink } from "../e2eTestUtils/helpers";
|
||||
|
||||
export class AuthPage {
|
||||
readonly page: Page
|
||||
readonly emailInputField: Locator
|
||||
readonly passwordInputField: Locator
|
||||
readonly passwordConfirmInputField: Locator
|
||||
readonly ageInputField: Locator
|
||||
readonly continueButton: Locator
|
||||
readonly createAccountButton: Locator
|
||||
readonly verifyCodeInputField: Locator
|
||||
readonly confirmCodeButton: Locator
|
||||
readonly page: Page;
|
||||
readonly emailInputField: Locator;
|
||||
readonly passwordInputField: Locator;
|
||||
readonly passwordConfirmInputField: Locator;
|
||||
readonly ageInputField: Locator;
|
||||
readonly continueButton: Locator;
|
||||
readonly createAccountButton: Locator;
|
||||
readonly verifyCodeInputField: Locator;
|
||||
readonly confirmCodeButton: Locator;
|
||||
|
||||
constructor(page: Page){
|
||||
this.page = page;
|
||||
this.emailInputField = page.locator('input[name="email"]');
|
||||
this.passwordInputField = process.env["E2E_TEST_ENV"] === "prod" ? page.locator('#password') : page.getByTestId('new-password-input-field');
|
||||
this.passwordConfirmInputField = process.env["E2E_TEST_ENV"] === "prod" ? page.locator('#vpassword') : page.getByTestId('verify-password-input-field');
|
||||
this.ageInputField = process.env["E2E_TEST_ENV"] === "prod" ? page.locator('#age') : page.getByTestId('age-input-field');
|
||||
this.continueButton = page.locator('#submit-btn');
|
||||
this.createAccountButton = page.getByRole('button', { name: 'Create account' });
|
||||
this.verifyCodeInputField = process.env["E2E_TEST_ENV"] === "prod" ? page.locator('div.card input') : page.getByTestId('confirm-signup-code-input-field');
|
||||
this.confirmCodeButton = page.getByRole('button', { name: 'Confirm' });
|
||||
}
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.emailInputField = page.locator('input[name="email"]');
|
||||
this.passwordInputField = page.locator("#password");
|
||||
this.passwordConfirmInputField = page.locator("#vpassword");
|
||||
this.ageInputField = page.locator("#age");
|
||||
this.continueButton = page.locator("#submit-btn");
|
||||
this.createAccountButton = page.getByRole("button", {
|
||||
name: "Create account",
|
||||
});
|
||||
this.verifyCodeInputField = page.locator("div.card input");
|
||||
this.confirmCodeButton = page.getByRole("button", { name: "Confirm" });
|
||||
}
|
||||
|
||||
async continue() {
|
||||
await this.continueButton.click();
|
||||
}
|
||||
async continue() {
|
||||
await this.continueButton.click();
|
||||
}
|
||||
|
||||
async enterVerificationCode(code: string){
|
||||
await this.verifyCodeInputField.fill(code);
|
||||
await this.confirmCodeButton.click();
|
||||
}
|
||||
async enterVerificationCode(code: string) {
|
||||
await this.verifyCodeInputField.fill(code);
|
||||
await this.confirmCodeButton.click();
|
||||
}
|
||||
|
||||
async enterEmail(email: string) {
|
||||
await this.emailInputField.fill(email);
|
||||
await this.continue();
|
||||
}
|
||||
async enterEmail(email: string) {
|
||||
await forceNonReactLink(this.page);
|
||||
await this.emailInputField.fill(email);
|
||||
await this.continue();
|
||||
}
|
||||
|
||||
async enterPassword() {
|
||||
await this.passwordInputField.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.continue();
|
||||
}
|
||||
async enterPassword() {
|
||||
await forceNonReactLink(this.page);
|
||||
await this.passwordInputField.fill(
|
||||
process.env.E2E_TEST_ACCOUNT_PASSWORD as string,
|
||||
);
|
||||
await this.continue();
|
||||
}
|
||||
|
||||
async login(email: string) {
|
||||
await this.enterEmail(email);
|
||||
await this.enterPassword();
|
||||
}
|
||||
async login(email: string) {
|
||||
await forceNonReactLink(this.page);
|
||||
await this.enterEmail(email);
|
||||
await this.enterPassword();
|
||||
}
|
||||
|
||||
async signUp(email: string){
|
||||
await this.enterEmail(email)
|
||||
await this.passwordInputField.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.passwordConfirmInputField.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.ageInputField.type("31");
|
||||
await this.createAccountButton.click()
|
||||
}
|
||||
async signUp(email: string) {
|
||||
await forceNonReactLink(this.page);
|
||||
await this.enterEmail(email);
|
||||
await this.passwordInputField.fill(
|
||||
process.env.E2E_TEST_ACCOUNT_PASSWORD as string,
|
||||
);
|
||||
await this.passwordConfirmInputField.fill(
|
||||
process.env.E2E_TEST_ACCOUNT_PASSWORD as string,
|
||||
);
|
||||
await this.ageInputField.type("31");
|
||||
await this.createAccountButton.click();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,243 +1,416 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
import { Locator, Page, expect } from "@playwright/test";
|
||||
import { checkAuthState, getVerificationCode } from "../e2eTestUtils/helpers";
|
||||
|
||||
export class DashboardPage {
|
||||
readonly page: Page
|
||||
readonly header: Locator
|
||||
readonly homeButton: Locator
|
||||
readonly FAQButton: Locator
|
||||
readonly newsButton: Locator
|
||||
readonly toastCloseButton: string
|
||||
readonly userMenuPopUp: Locator
|
||||
readonly userMenuLetter: Locator
|
||||
readonly getMoreProtectionButton: Locator
|
||||
readonly userMenuPopEmail: Locator
|
||||
readonly upgradeButton: Locator
|
||||
readonly upgradeNowButton: Locator
|
||||
readonly userMenuButton: Locator
|
||||
readonly signOutButton: Locator
|
||||
readonly signOutToastAlert: Locator
|
||||
readonly bottomUgradeBanner: Locator
|
||||
readonly relayExtensionBanner: Locator
|
||||
readonly dashBoardWithoutMasks: Locator
|
||||
readonly dashBoardWithoutMasksEmail: Locator
|
||||
readonly generateNewMaskButton: Locator
|
||||
readonly emailsForwardedAmount: Locator
|
||||
readonly emailsBlockedAmount: Locator
|
||||
readonly emailMasksUsedAmount: Locator
|
||||
readonly maskCard: Locator
|
||||
readonly maskCardString: string
|
||||
readonly maskCardExpanded: Locator
|
||||
readonly maskCardExpandButton: Locator
|
||||
readonly maskCardHeader: Locator
|
||||
readonly maskCardForwardEmail: Locator
|
||||
readonly maskCardCreatedDate: Locator
|
||||
readonly maskCardGeneratedEmail: Locator
|
||||
readonly maskCardForwardedAmount: Locator
|
||||
readonly maskCardRepliesAmount: Locator
|
||||
readonly maskCardBlockedAmount: Locator
|
||||
readonly maskCardDeleteButton: Locator
|
||||
readonly maskCardCancelButton: Locator
|
||||
readonly dashboardPageWithoutHeader: Locator
|
||||
readonly maskCardDeleteDialogModal: Locator
|
||||
readonly maskCardDeleteDialogModalEmailString: Locator
|
||||
readonly maskCardDeleteDialogModalGeneratedEmail: Locator
|
||||
readonly maskCardDeleteConfirmationCheckbox: Locator
|
||||
readonly maskCardFinalDeleteButton: Locator
|
||||
readonly maxMaskLimitButton: Locator
|
||||
readonly page: Page;
|
||||
readonly header: Locator;
|
||||
readonly homeButton: Locator;
|
||||
readonly FAQButton: Locator;
|
||||
readonly newsButton: Locator;
|
||||
readonly userMenuPopUp: Locator;
|
||||
readonly userMenuLetter: Locator;
|
||||
readonly getMoreProtectionButton: Locator;
|
||||
readonly userMenuPopEmail: Locator;
|
||||
readonly upgradeButton: Locator;
|
||||
readonly upgradeNowButton: Locator;
|
||||
readonly userMenuButton: Locator;
|
||||
readonly signOutButton: Locator;
|
||||
readonly signOutToastAlert: Locator;
|
||||
readonly bottomUgradeBanner: Locator;
|
||||
readonly relayExtensionBanner: Locator;
|
||||
readonly dashBoardWithoutMasks: Locator;
|
||||
readonly dashBoardWithoutMasksEmail: Locator;
|
||||
readonly generateNewMaskButton: Locator;
|
||||
readonly emailsForwardedAmount: Locator;
|
||||
readonly emailsBlockedAmount: Locator;
|
||||
readonly emailMasksUsedAmount: Locator;
|
||||
readonly maskCard: Locator;
|
||||
readonly maskCardString: string;
|
||||
readonly maskCardExpanded: Locator;
|
||||
readonly maskCardExpandButton: Locator;
|
||||
readonly maskCardHeader: Locator;
|
||||
readonly maskCardGeneratedEmail: Locator;
|
||||
readonly maskCardForwardedAmount: Locator;
|
||||
readonly maskCardRepliesAmount: Locator;
|
||||
readonly maskCardBlockedAmount: Locator;
|
||||
readonly maskCardDeleteButton: Locator;
|
||||
readonly maskCardCancelButton: Locator;
|
||||
readonly dashboardPageWithoutHeader: Locator;
|
||||
readonly maskCardDeleteDialogModal: Locator;
|
||||
readonly maskCardDeleteDialogModalGeneratedEmail: Locator;
|
||||
readonly maskCardFinalDeleteButton: Locator;
|
||||
readonly maxMaskLimitButton: Locator;
|
||||
readonly maxMaskBannerText: Locator;
|
||||
readonly generateNewMaskPremiumButton: Locator;
|
||||
readonly premiumRandomMask: Locator;
|
||||
readonly closeCornerUpsell: Locator;
|
||||
readonly blockPromotions: Locator;
|
||||
readonly blockAll: Locator;
|
||||
readonly blockLevelAllLabel: Locator;
|
||||
readonly blockLevelPromosLabel: Locator;
|
||||
readonly premiumDomainMask: Locator;
|
||||
readonly customMaskInput: Locator;
|
||||
readonly generateCustomMaskConfirm: Locator;
|
||||
readonly customMaskSuccessHeader: Locator;
|
||||
readonly customMaskDoneButton: Locator;
|
||||
readonly maskCardBottomMeta: Locator;
|
||||
readonly maskCardTrackersCount: Locator;
|
||||
|
||||
constructor(page: Page){
|
||||
this.page = page;
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
// dashboard header elements
|
||||
this.header = page.locator('div header').first();
|
||||
this.FAQButton = page.locator('header >> text=FAQ')
|
||||
this.newsButton = page.locator('header >> text=News')
|
||||
this.homeButton = page.locator('header >> text=Email Masks')
|
||||
this.userMenuButton = page.locator('//div[starts-with(@class, "UserMenu_wrapper")]')
|
||||
this.userMenuPopUp = page.locator('//ul[starts-with(@class, "UserMenu_popup")]')
|
||||
this.userMenuLetter = page.locator('//div[starts-with(@class, "UserMenu_wrapper")]')
|
||||
this.userMenuPopEmail = page.locator('//span[starts-with(@class, "UserMenu_account")]/b')
|
||||
this.toastCloseButton = '//div[starts-with(@class, "Layout_close")]'
|
||||
this.signOutButton = page.locator('button:has-text("Sign Out")').first()
|
||||
this.signOutToastAlert = page.locator('//div[@class="Toastify__toast-body"]')
|
||||
// dashboard header elements
|
||||
this.header = page.locator("div header").first();
|
||||
this.FAQButton = page.getByText("FAQ").first();
|
||||
this.newsButton = page.getByText("News");
|
||||
this.homeButton = page.getByRole("link", { name: "Email masks" });
|
||||
this.userMenuButton = page.locator(
|
||||
'//div[starts-with(@class, "UserMenu_wrapper")]',
|
||||
);
|
||||
this.userMenuPopUp = page.locator(
|
||||
'//ul[starts-with(@class, "UserMenu_popup")]',
|
||||
);
|
||||
this.userMenuLetter = page.locator(
|
||||
'//div[starts-with(@class, "UserMenu_wrapper")]',
|
||||
);
|
||||
this.userMenuPopEmail = page.locator(
|
||||
'//span[starts-with(@class, "UserMenu_account")]/b',
|
||||
);
|
||||
this.signOutButton = page.locator('button:has-text("Sign Out")').first();
|
||||
this.signOutToastAlert = page.locator(
|
||||
'//div[@class="Toastify__toast-body"]',
|
||||
);
|
||||
|
||||
// dashboard elements
|
||||
this.upgradeNowButton = page.locator('a:has-text("Upgrade Now")')
|
||||
this.upgradeButton = page.locator('a:has-text("Upgrade")').first()
|
||||
this.getMoreProtectionButton = page.locator(':has-text("Get more protection")')
|
||||
this.dashboardPageWithoutHeader = page.locator('//main[starts-with(@class, "profile_profile-wrapper")]')
|
||||
this.emailsForwardedAmount = page.locator('(//dd[starts-with(@class, "profile_value")])[3]')
|
||||
this.emailsBlockedAmount = page.locator('(//dd[starts-with(@class, "profile_value")])[2]')
|
||||
this.emailMasksUsedAmount = page.locator('(//dd[starts-with(@class, "profile_value")])[1]')
|
||||
this.generateNewMaskButton = page.locator('button:has-text("Generate new mask")')
|
||||
this.maxMaskLimitButton = page.locator('//div[starts-with(@class, "AliasList_controls")]//a[starts-with(@class, "Button_button")]')
|
||||
this.bottomUgradeBanner = page.locator('//div[starts-with(@class, "profile_bottom-banner-wrapper")]')
|
||||
this.relayExtensionBanner = page.locator('//section[starts-with(@class, "profile_banners-wrapper")]/div')
|
||||
this.dashBoardWithoutMasks = page.locator('//section[starts-with(@class, "Onboarding_wrapper")]')
|
||||
this.dashBoardWithoutMasksEmail = page.locator('//section[starts-with(@class, "profile_no-premium-header")]')
|
||||
// dashboard elements
|
||||
this.upgradeNowButton = page.locator('a:has-text("Upgrade Now")');
|
||||
this.upgradeButton = page.locator('a:has-text("Upgrade")').first();
|
||||
this.getMoreProtectionButton = page.locator(
|
||||
':has-text("Get more protection")',
|
||||
);
|
||||
this.dashboardPageWithoutHeader = page.locator(
|
||||
'//main[starts-with(@class, "profile_profile-wrapper")]',
|
||||
);
|
||||
this.emailsForwardedAmount = page.locator(
|
||||
'(//dd[starts-with(@class, "profile_value")])[3]',
|
||||
);
|
||||
this.emailsBlockedAmount = page.locator(
|
||||
'(//dd[starts-with(@class, "profile_value")])[2]',
|
||||
);
|
||||
this.emailMasksUsedAmount = page.locator(
|
||||
'(//dd[starts-with(@class, "profile_value")])[1]',
|
||||
);
|
||||
this.generateNewMaskButton = page.getByTitle("Generate new mask");
|
||||
this.generateNewMaskPremiumButton = page.locator(
|
||||
"button:has-text('Generate new mask')",
|
||||
);
|
||||
this.maxMaskLimitButton = page.getByText("Get unlimited email masks");
|
||||
this.maxMaskBannerText = page.locator(
|
||||
'//p[starts-with(@class, "profile_upsell-banner-description")]',
|
||||
);
|
||||
this.premiumRandomMask = page.locator('//li[@data-key="random"]');
|
||||
this.premiumDomainMask = page.locator('//li[@data-key="custom"]');
|
||||
this.closeCornerUpsell = page.locator(
|
||||
'//button[starts-with(@class, "CornerNotification_close-button")]',
|
||||
);
|
||||
this.bottomUgradeBanner = page.locator(
|
||||
'//div[starts-with(@class, "profile_bottom-banner-wrapper")]',
|
||||
);
|
||||
this.relayExtensionBanner = page.locator(
|
||||
'//div[contains(@class, "is-hidden-with-addon")]',
|
||||
);
|
||||
this.dashBoardWithoutMasks = page.locator(
|
||||
'//section[starts-with(@class, "Onboarding_wrapper")]',
|
||||
);
|
||||
this.dashBoardWithoutMasksEmail = page.locator(
|
||||
'//section[starts-with(@class, "profile_no-premium-header")]',
|
||||
);
|
||||
|
||||
// mask card elements
|
||||
this.maskCard = page.getByRole('button', { name: 'Generate new mask' })
|
||||
this.maskCardString = '//div[starts-with(@class, "MaskCard_card")]'
|
||||
this.maskCardExpanded = page.locator('//button[starts-with(@class, "MaskCard_expand")]')
|
||||
this.maskCardExpandButton = page.locator('//button[starts-with(@class, "MaskCard_expand")]')
|
||||
this.maskCardHeader = page.locator('//div[starts-with(@class, "MaskCard_summary")]')
|
||||
this.maskCardGeneratedEmail = page.locator('//button[starts-with(@class, "MaskCard_copy")]/samp').first()
|
||||
this.maskCardForwardEmail = page.locator('//div[starts-with(@class, "Alias_forward-target")]')
|
||||
this.maskCardCreatedDate = page.locator('//div[starts-with(@class, "Alias_date-created")]')
|
||||
this.maskCardForwardedAmount = page.locator('//div[contains(@class, "MaskCard_forwarded")]/dd').first()
|
||||
this.maskCardRepliesAmount = page.locator('(//span[contains(@class, "Alias_blocked-stat")])[2]')
|
||||
this.maskCardBlockedAmount = page.locator('(//span[contains(@class, "Alias_blocked-stat")])[1]')
|
||||
this.maskCardDeleteButton = page.locator('button:has-text("Delete")')
|
||||
this.maskCardCancelButton = page.locator('button:has-text("Cancel")')
|
||||
this.maskCardDeleteDialogModal = page.locator('//div[starts-with(@class, "AliasDeletionButton_dialog-wrapper")]')
|
||||
this.maskCardDeleteDialogModalEmailString = page.locator('//div[starts-with(@class, "AliasDeletionButton_dialog-wrapper")]//strong')
|
||||
this.maskCardDeleteDialogModalGeneratedEmail = page.locator('//div[starts-with(@class, "AliasDeletionButton_dialog-wrapper")]//samp')
|
||||
this.maskCardDeleteConfirmationCheckbox = page.locator('#confirmDeletion')
|
||||
this.maskCardFinalDeleteButton = page.locator('//button[contains(@class, "Button_is-destructive")]')
|
||||
// mask card elements
|
||||
this.maskCard = page.getByRole("button", { name: "Generate new mask" });
|
||||
this.maskCardString = '//div[starts-with(@class, "MaskCard_card")]';
|
||||
this.maskCardExpanded = page.locator(
|
||||
'//button[starts-with(@class, "MaskCard_expand")]',
|
||||
);
|
||||
this.maskCardExpandButton = page.locator(
|
||||
'//button[starts-with(@class, "MaskCard_expand")]',
|
||||
);
|
||||
this.maskCardHeader = page.locator(
|
||||
'//div[starts-with(@class, "MaskCard_summary")]',
|
||||
);
|
||||
this.maskCardGeneratedEmail = page
|
||||
.locator('//button[starts-with(@class, "MaskCard_copy")]/samp')
|
||||
.first();
|
||||
this.maskCardBottomMeta = page.locator(
|
||||
'//div[starts-with(@class, "MaskCard_meta")]',
|
||||
);
|
||||
this.maskCardForwardedAmount = page
|
||||
.locator('//div[contains(@class, "MaskCard_forwarded")]/dd')
|
||||
.first();
|
||||
this.maskCardTrackersCount = page
|
||||
.locator('//div[contains(@class, "MaskCard_trackers-removed-stat")]/dd')
|
||||
.first();
|
||||
this.maskCardRepliesAmount = page.locator(
|
||||
'(//span[contains(@class, "Alias_blocked-stat")])[2]',
|
||||
);
|
||||
this.maskCardBlockedAmount = page.locator(
|
||||
'(//span[contains(@class, "Alias_blocked-stat")])[1]',
|
||||
);
|
||||
this.maskCardDeleteButton = page.locator('button:has-text("Delete")');
|
||||
this.maskCardCancelButton = page.locator('button:has-text("Cancel")');
|
||||
this.maskCardDeleteDialogModal = page.locator(
|
||||
'//div[starts-with(@class, "AliasDeletionButtonPermanent_dialog-wrapper")]',
|
||||
);
|
||||
this.maskCardDeleteDialogModalGeneratedEmail = page.locator(
|
||||
'//div[starts-with(@class, "AliasDeletionButtonPermanent_dialog-wrapper")]//samp',
|
||||
);
|
||||
this.maskCardFinalDeleteButton = page.locator(
|
||||
'//button[contains(@class, "Button_is-destructive")]',
|
||||
);
|
||||
this.blockPromotions = page
|
||||
.locator(
|
||||
'//div[starts-with(@class, "MaskCard_block-level-segmented-control")]',
|
||||
)
|
||||
.first()
|
||||
.getByText("Promotions")
|
||||
.first();
|
||||
this.blockLevelPromosLabel = page
|
||||
.locator('//div[starts-with(@class, "MaskCard_block-level-label")]')
|
||||
.getByText("Blocking promo emails")
|
||||
.first();
|
||||
this.blockLevelAllLabel = page
|
||||
.locator('//div[starts-with(@class, "MaskCard_block-level-label")]')
|
||||
.getByText("Blocking all emails")
|
||||
.first();
|
||||
this.blockAll = page
|
||||
.locator(
|
||||
'//div[starts-with(@class, "MaskCard_block-level-segmented-control")]',
|
||||
)
|
||||
.first()
|
||||
.getByText("All")
|
||||
.first();
|
||||
this.customMaskInput = page.getByPlaceholder("Enter text");
|
||||
this.generateCustomMaskConfirm = page.getByRole("button", {
|
||||
name: "Generate mask",
|
||||
});
|
||||
this.customMaskSuccessHeader = page.getByRole("heading", {
|
||||
name: "Success!",
|
||||
});
|
||||
this.customMaskDoneButton = page.getByRole("button", { name: "Done" });
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.page.goto("/accounts/profile/");
|
||||
}
|
||||
|
||||
async skipOnboarding() {
|
||||
const onboardingElem = this.page.getByRole("button", { name: "Skip" });
|
||||
|
||||
if (await onboardingElem.isVisible({ timeout: 6000 })) {
|
||||
await onboardingElem.click();
|
||||
}
|
||||
}
|
||||
|
||||
async generatePremiumDomainMask(numberOfMasks = 1) {
|
||||
if (numberOfMasks === 0) {
|
||||
return;
|
||||
}
|
||||
await this.generateNewMaskPremiumButton.click();
|
||||
await this.premiumDomainMask.click();
|
||||
|
||||
await this.customMaskInput.fill(`${Date.now()}`);
|
||||
await this.generateCustomMaskConfirm.click();
|
||||
|
||||
expect(await this.customMaskSuccessHeader.textContent()).toContain(
|
||||
"Success",
|
||||
);
|
||||
await this.customMaskDoneButton.click();
|
||||
await this.generatePremiumDomainMask(numberOfMasks - 1);
|
||||
}
|
||||
|
||||
async generateMask(numberOfMasks = 1, isPremium = false) {
|
||||
// check if max number of masks have been created
|
||||
if (numberOfMasks === 0) {
|
||||
return;
|
||||
}
|
||||
const generateMaskBtn = isPremium
|
||||
? this.generateNewMaskPremiumButton
|
||||
: this.generateNewMaskButton;
|
||||
|
||||
// generate a new mask and confirm
|
||||
await generateMaskBtn.click();
|
||||
|
||||
const randomMaskShown = await this.premiumRandomMask.isVisible();
|
||||
if (randomMaskShown) {
|
||||
await this.premiumRandomMask.click();
|
||||
}
|
||||
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 });
|
||||
|
||||
// randomize between 1.5-2.5 secs between each generate to deal with issue of multiple quick clicks
|
||||
await this.page.waitForTimeout(Math.random() * 2500 + 1500);
|
||||
await this.generateMask(numberOfMasks - 1, isPremium);
|
||||
}
|
||||
|
||||
async upgrade() {
|
||||
await Promise.all([
|
||||
this.page.waitForURL(/.*\/accounts\/profile\/.*/),
|
||||
this.upgradeButton.click(),
|
||||
]);
|
||||
}
|
||||
|
||||
async upgradeNow() {
|
||||
await Promise.all([this.upgradeNowButton.click()]);
|
||||
}
|
||||
|
||||
async maybeDeleteMasks(clearAll = true, numberOfMasks = 1) {
|
||||
let isExpanded = false;
|
||||
|
||||
try {
|
||||
numberOfMasks = await this.page.locator(this.maskCardString).count();
|
||||
} catch (err) {}
|
||||
|
||||
// check number of masks available
|
||||
if (numberOfMasks === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.page.goto('/accounts/profile/');
|
||||
if (await this.closeCornerUpsell.isVisible()) {
|
||||
await this.closeCornerUpsell.click();
|
||||
}
|
||||
|
||||
async generateMask(numberOfMasks = 1){
|
||||
// check if max number of masks have been created
|
||||
if(numberOfMasks === 0){
|
||||
return
|
||||
}
|
||||
// if clear all, check if there's an expanded mask card
|
||||
if (clearAll) {
|
||||
try {
|
||||
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 });
|
||||
} catch (error) {
|
||||
console.error("There are no masks to delete");
|
||||
return;
|
||||
}
|
||||
|
||||
// generate a new mask and confirm
|
||||
await this.generateNewMaskButton.click()
|
||||
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 })
|
||||
|
||||
// randomize between 1.5-2.5 secs between each generate to deal with issue of multiple quick clicks
|
||||
await this.page.waitForTimeout((Math.random() * 2500) + 1500)
|
||||
await this.generateMask(numberOfMasks - 1)
|
||||
try {
|
||||
isExpanded = await this.page
|
||||
.getByRole("button", { expanded: true })
|
||||
.first()
|
||||
.isVisible();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async upgrade(){
|
||||
await Promise.all([
|
||||
this.page.waitForURL(/.*\/accounts\/profile\/.*/),
|
||||
this.upgradeButton.click()
|
||||
]);
|
||||
// locate mask expand button only if mask is not already expanded
|
||||
if (numberOfMasks && !isExpanded) {
|
||||
try {
|
||||
await this.maskCardExpanded.first().click();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async upgradeNow(){
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation(),
|
||||
this.upgradeNowButton.click()
|
||||
]);
|
||||
// delete flow
|
||||
if (numberOfMasks) {
|
||||
const currentMaskCardDeleteButton = this.page
|
||||
.locator('button:has-text("Delete")')
|
||||
.first();
|
||||
await currentMaskCardDeleteButton.click();
|
||||
await this.maskCardFinalDeleteButton.click();
|
||||
}
|
||||
|
||||
async maybeCloseToaster(){
|
||||
try {
|
||||
await this.page.waitForSelector(this.toastCloseButton, { timeout: 2000 })
|
||||
await this.page.locator(this.toastCloseButton).click()
|
||||
} catch (error) {
|
||||
console.error('No Toaster, please proceed')
|
||||
}
|
||||
// wait for 500 ms and run flow again with the next masks
|
||||
await this.page.waitForTimeout(500);
|
||||
await this.maybeDeleteMasks(true, numberOfMasks - 1);
|
||||
}
|
||||
|
||||
async generateOneRandomMask() {
|
||||
// reset data
|
||||
await this.open();
|
||||
await checkAuthState(this.page);
|
||||
await this.skipOnboarding();
|
||||
await this.maybeDeleteMasks();
|
||||
|
||||
// create mask and use generated mask email to test email forwarding feature
|
||||
await this.generateMask(1);
|
||||
const generatedMaskEmail = await this.maskCardGeneratedEmail.textContent();
|
||||
|
||||
return generatedMaskEmail;
|
||||
}
|
||||
|
||||
async checkForwardedEmailCount(attempts = 10) {
|
||||
if (attempts === 0) {
|
||||
throw new Error("Email forwarded count did not update");
|
||||
}
|
||||
|
||||
async maybeDeleteMasks(clearAll = true, numberOfMasks = 1){
|
||||
let isExpanded = false
|
||||
await this.forceRefresh();
|
||||
|
||||
try {
|
||||
numberOfMasks = await this.page.locator(this.maskCardString).count()
|
||||
} catch(err){}
|
||||
|
||||
// check number of masks available
|
||||
if(numberOfMasks === 0){
|
||||
return
|
||||
}
|
||||
|
||||
// if clear all, check if there's an expanded mask card
|
||||
if(clearAll){
|
||||
try {
|
||||
await this.page.waitForSelector(this.maskCardString, { timeout: 3000 })
|
||||
} catch (error) {
|
||||
console.error('There are no masks to delete')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isExpanded = await this.page.getByRole('button', { expanded: true }).first().isVisible()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// locate mask expand button only if mask is not already expanded
|
||||
if(numberOfMasks && !isExpanded){
|
||||
try {
|
||||
await this.maskCardExpanded.first().click()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// delete flow
|
||||
if(numberOfMasks){
|
||||
const currentMaskCardDeleteButton = this.page.locator('button:has-text("Delete")').first()
|
||||
await currentMaskCardDeleteButton.click()
|
||||
await this.maskCardDeleteConfirmationCheckbox.click()
|
||||
await this.maskCardFinalDeleteButton.click()
|
||||
}
|
||||
|
||||
// wait for 500 ms and run flow again with the next masks
|
||||
await this.page.waitForTimeout(500)
|
||||
await this.maybeDeleteMasks(true, numberOfMasks - 1)
|
||||
// check if card is expanded
|
||||
if (
|
||||
!(await this.page
|
||||
.getByRole("button", { expanded: true })
|
||||
.first()
|
||||
.isVisible())
|
||||
) {
|
||||
await this.maskCardExpanded.first().click();
|
||||
}
|
||||
|
||||
async sendMaskEmail(){
|
||||
// reset data
|
||||
await this.open()
|
||||
await checkAuthState(this.page)
|
||||
await this.maybeDeleteMasks()
|
||||
|
||||
// create mask and use generated mask email to test email forwarding feature
|
||||
await this.generateMask(1)
|
||||
const generatedMaskEmail = await this.maskCardGeneratedEmail.textContent()
|
||||
|
||||
// TODO: Replace with a page under control of Relay team
|
||||
await this.page.goto("https://monitor.firefox.com/", { waitUntil: 'networkidle' })
|
||||
await this.page.locator('#scan-email-address').fill(generatedMaskEmail as string)
|
||||
await this.page.locator('button.primary').click()
|
||||
await this.page.waitForURL('**/scan**')
|
||||
|
||||
await this.page.getByRole('link', {name: 'Get alerts about new breaches'}).click()
|
||||
|
||||
await this.page.locator('input[name=email]').fill(generatedMaskEmail as string)
|
||||
await this.page.locator('#submit-btn').click()
|
||||
|
||||
await this.page.locator('#password').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.page.locator('#vpassword').fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.page.locator('#age').fill('31');
|
||||
await this.page.locator('#submit-btn').click()
|
||||
await this.page.waitForURL('**/confirm_signup_code**')
|
||||
|
||||
// verification email from fxa to generatedMaskEmail should be forwarded to E2E_TEST_ACCOUNT_FREE
|
||||
await getVerificationCode(process.env.E2E_TEST_ACCOUNT_FREE as string, this.page)
|
||||
// check the forward emails count, if not 0, return the current value
|
||||
const forwardCount = await this.maskCardForwardedAmount.textContent();
|
||||
if (forwardCount !== "0") {
|
||||
return forwardCount;
|
||||
}
|
||||
|
||||
async checkForwardedEmailCount(attempts = 10) {
|
||||
if (attempts === 0) {
|
||||
throw new Error('Email forwarded count did not update');
|
||||
}
|
||||
return this.checkForwardedEmailCount(attempts - 1);
|
||||
}
|
||||
|
||||
// force a re-request of relayaddresses
|
||||
await this.FAQButton.click()
|
||||
await this.homeButton.click()
|
||||
|
||||
// check if card is expanded
|
||||
if(!(await this.page.getByRole('button', { expanded: true }).first().isVisible())){
|
||||
await this.maskCardExpanded.first().click()
|
||||
}
|
||||
|
||||
// check the forward emails count, if not 0, return the current value
|
||||
const forwardCount = await this.maskCardForwardedAmount.textContent()
|
||||
if(forwardCount !== "0"){
|
||||
return forwardCount;
|
||||
}
|
||||
|
||||
await this.page.waitForTimeout(1000)
|
||||
return this.checkForwardedEmailCount(attempts - 1)
|
||||
async setTrackersRemovalOpt() {
|
||||
await this.page.goto("/accounts/settings/");
|
||||
const trackersBox = this.page.locator("#tracker-removal");
|
||||
if (!(await trackersBox.isChecked())) {
|
||||
await trackersBox.check();
|
||||
}
|
||||
await this.page.waitForTimeout(1000);
|
||||
await this.page.getByRole("button", { name: "Save" }).click();
|
||||
}
|
||||
|
||||
async signUpForPageWithTrackers(mask: string) {
|
||||
await this.page.goto("https://pages.developmentthatpays.com/cheatsheets/scrum-kanban");
|
||||
await this.page.getByPlaceholder("First Name").fill("relay-testing");
|
||||
await this.page.getByPlaceholder("Email Address").fill(mask);
|
||||
await this.page
|
||||
.getByRole("button", { name: "let me have that cheat sheet!" })
|
||||
.click();
|
||||
await this.page.waitForTimeout(5000);
|
||||
const captchaShown = await this.page.isVisible(".seva-overlay");
|
||||
if (captchaShown) {
|
||||
throw new Error("Unable to continue test, captcha was shown");
|
||||
}
|
||||
await this.page.waitForURL("**/scrum-vs-kanban/");
|
||||
await this.open();
|
||||
}
|
||||
|
||||
async forceRefresh() {
|
||||
// force a re-request of relayaddresses
|
||||
await this.FAQButton.click();
|
||||
await this.homeButton.click();
|
||||
}
|
||||
|
||||
async checkTrackersCount(attempts = 10) {
|
||||
if (attempts === 0) {
|
||||
throw new Error("Email trackers count did not update");
|
||||
}
|
||||
|
||||
await this.forceRefresh();
|
||||
|
||||
// check if card is expanded
|
||||
if (
|
||||
!(await this.page
|
||||
.getByRole("button", { expanded: true })
|
||||
.first()
|
||||
.isVisible())
|
||||
) {
|
||||
await this.maskCardExpanded.first().click();
|
||||
}
|
||||
|
||||
// check the forward emails count, if not 0, return the current value
|
||||
const trackersCount = await this.maskCardTrackersCount.textContent();
|
||||
if (trackersCount !== "0") {
|
||||
return trackersCount;
|
||||
}
|
||||
|
||||
this.page.waitForTimeout(500);
|
||||
return this.checkTrackersCount(attempts - 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,130 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
|
||||
export class LandingPage {
|
||||
readonly page: Page
|
||||
readonly header: Locator
|
||||
readonly FAQButton: Locator
|
||||
readonly homeButton: Locator
|
||||
readonly signUpButton: Locator
|
||||
readonly subscriptionTitle: Locator
|
||||
readonly planPricingSignUpButton: Locator
|
||||
readonly signInButton: Locator
|
||||
readonly firefoxAppsServices: Locator
|
||||
readonly firefoxAppsServicesHeading: Locator
|
||||
readonly firefoxLogo: Locator
|
||||
readonly page: Page;
|
||||
readonly header: Locator;
|
||||
readonly FAQButton: Locator;
|
||||
readonly homeButton: Locator;
|
||||
readonly signUpButton: Locator;
|
||||
readonly subscriptionTitle: Locator;
|
||||
readonly planMatrixDesktop: Locator;
|
||||
readonly planMatrixMobile: Locator;
|
||||
yearlyEmailsPlan: Locator;
|
||||
monthlyEmailsPlan: Locator;
|
||||
emailsPlanSubmit: Locator;
|
||||
yearlyEmailsPhonesBundle: Locator;
|
||||
monthlyEmailsPhonesBundle: Locator;
|
||||
emailsPhonesBundleSubmit: Locator;
|
||||
vpnBundleSubmit: Locator;
|
||||
readonly signInButton: Locator;
|
||||
readonly firefoxAppsServices: Locator;
|
||||
readonly firefoxAppsServicesHeading: Locator;
|
||||
readonly firefoxLogo: Locator;
|
||||
readonly heroSection: Locator;
|
||||
|
||||
constructor(page: Page){
|
||||
this.page = page
|
||||
this.header = page.locator('#overlayProvider header')
|
||||
this.FAQButton = page.getByRole('link', { name: 'FAQ', exact: true })
|
||||
this.homeButton = page.getByRole('link', { name: 'Home', exact: true })
|
||||
this.signUpButton = page.locator('a:has-text("Sign Up")').first()
|
||||
this.planPricingSignUpButton = page.locator('//a[contains(@class, "Plans_premium-plan")]/div')
|
||||
this.subscriptionTitle = page.locator('[data-testid="subscription-create-title"]')
|
||||
this.signInButton = page.locator('a:has-text("Sign In")')
|
||||
this.firefoxAppsServices = page.getByRole('button', { name: 'Firefox apps and services' })
|
||||
this.firefoxAppsServicesHeading = page.getByRole('heading', { name: 'Firefox is tech that fights for your online privacy.' })
|
||||
this.firefoxLogo = page.locator('//a[starts-with(@class, "Layout_logo")]')
|
||||
}
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.heroSection = page.locator("#hero");
|
||||
this.header = page.locator("#overlayProvider header");
|
||||
this.FAQButton = page.getByRole("link", { name: "FAQ", exact: true });
|
||||
this.homeButton = page.getByRole("link", { name: "Home", exact: true });
|
||||
this.signUpButton = page.locator('a:has-text("Sign Up")').first();
|
||||
this.planMatrixDesktop = page.locator(
|
||||
'//table[starts-with(@class, "PlanMatrix_desktop")]',
|
||||
);
|
||||
this.planMatrixMobile = page.locator(
|
||||
'//div[starts-with(@class, "PlanMatrix_mobile")]',
|
||||
);
|
||||
this.subscriptionTitle = page.locator(
|
||||
'[data-testid="subscription-create-title"]',
|
||||
);
|
||||
this.signInButton = page.locator('a:has-text("Sign In")');
|
||||
this.firefoxAppsServices = page.getByRole("button", {
|
||||
name: "Firefox apps and services",
|
||||
});
|
||||
this.firefoxAppsServicesHeading = page.getByRole("heading", {
|
||||
name: "Firefox is tech that fights for your online privacy.",
|
||||
});
|
||||
this.firefoxLogo = page.locator('//a[starts-with(@class, "Layout_logo")]');
|
||||
this.setPlanElements();
|
||||
}
|
||||
|
||||
async open(){
|
||||
await this.page.goto(process.env.E2E_TEST_BASE_URL as string)
|
||||
}
|
||||
async setPlanElements() {
|
||||
const isDesktop = await this.planMatrixDesktop.isVisible();
|
||||
const currPlanMatrix = isDesktop
|
||||
? this.planMatrixDesktop
|
||||
: this.planMatrixMobile;
|
||||
this.yearlyEmailsPlan = currPlanMatrix
|
||||
.locator('[id*="tab-yearly"]')
|
||||
.first();
|
||||
this.monthlyEmailsPlan = currPlanMatrix
|
||||
.locator('[id*="tab-monthly"]')
|
||||
.first();
|
||||
this.emailsPlanSubmit = currPlanMatrix.getByText("Sign Up").first();
|
||||
this.yearlyEmailsPhonesBundle = currPlanMatrix
|
||||
.locator('[id*="tab-yearly"]')
|
||||
.nth(1);
|
||||
this.monthlyEmailsPhonesBundle = currPlanMatrix
|
||||
.locator('[id*="tab-monthly"]')
|
||||
.nth(1);
|
||||
this.emailsPhonesBundleSubmit = currPlanMatrix.getByText("Sign Up").nth(1);
|
||||
this.vpnBundleSubmit = currPlanMatrix.getByText("Sign Up").nth(2);
|
||||
}
|
||||
|
||||
async goHome(){
|
||||
await Promise.all([
|
||||
this.page.waitForLoadState("networkidle"),
|
||||
this.homeButton.click()
|
||||
]);
|
||||
}
|
||||
async open() {
|
||||
await this.page.goto(process.env.E2E_TEST_BASE_URL as string);
|
||||
}
|
||||
|
||||
async goToFAQ(){
|
||||
await Promise.all([
|
||||
this.page.waitForURL(/faq/),
|
||||
this.FAQButton.click()
|
||||
]);
|
||||
}
|
||||
async goHome() {
|
||||
await Promise.all([
|
||||
this.page.waitForLoadState("networkidle"),
|
||||
this.homeButton.click(),
|
||||
]);
|
||||
}
|
||||
|
||||
async goToSignUp(){
|
||||
await this.signUpButton.click()
|
||||
}
|
||||
async goToFAQ() {
|
||||
await Promise.all([this.page.waitForURL(/faq/), this.FAQButton.click()]);
|
||||
}
|
||||
|
||||
async selectPricingPlanSignUp(){
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation(),
|
||||
this.planPricingSignUpButton.click()
|
||||
]);
|
||||
}
|
||||
async goToSignUp() {
|
||||
await this.signUpButton.click();
|
||||
}
|
||||
|
||||
async goToSignIn(){
|
||||
await this.signInButton.click()
|
||||
}
|
||||
async selectYearlyEmailsPlan() {
|
||||
await this.yearlyEmailsPlan.click();
|
||||
await this.emailsPlanSubmit.click();
|
||||
}
|
||||
|
||||
async openFirefoxAppsServices(){
|
||||
await this.page.waitForLoadState("networkidle")
|
||||
await this.firefoxAppsServices.click({ force: true })
|
||||
}
|
||||
async selectMonthlyEmailsPlan() {
|
||||
await this.monthlyEmailsPlan.click();
|
||||
await this.emailsPlanSubmit.click();
|
||||
}
|
||||
|
||||
async clickFirefoxLogo(){
|
||||
await this.firefoxLogo.click()
|
||||
}
|
||||
}
|
||||
async selectYearlyPhonesEmailsBundle() {
|
||||
await this.yearlyEmailsPhonesBundle.click();
|
||||
await this.emailsPhonesBundleSubmit.click();
|
||||
}
|
||||
|
||||
async selectMonthlyPhonesEmailsBundle() {
|
||||
await this.monthlyEmailsPhonesBundle.click();
|
||||
await this.emailsPhonesBundleSubmit.click();
|
||||
}
|
||||
|
||||
async selectVpnBundlePlan() {
|
||||
await this.vpnBundleSubmit.click();
|
||||
}
|
||||
|
||||
async goToSignIn() {
|
||||
await this.signInButton.click();
|
||||
await this.page.waitForURL("**/oauth/**");
|
||||
}
|
||||
|
||||
async openFirefoxAppsServices() {
|
||||
await this.page.waitForLoadState("networkidle");
|
||||
await this.firefoxAppsServices.click({ force: true });
|
||||
}
|
||||
|
||||
async clickFirefoxLogo() {
|
||||
await this.firefoxLogo.click();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import { Page, Locator } from "@playwright/test";
|
||||
import { getVerificationCode } from "../e2eTestUtils/helpers";
|
||||
|
||||
export class MozillaMonitorPage {
|
||||
readonly page: Page;
|
||||
readonly monitorSignUpInput: Locator;
|
||||
readonly monitorSignUpButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.monitorSignUpInput = page
|
||||
.locator("//form[contains(@class, 'SignUpForm_form')]/input")
|
||||
.first();
|
||||
this.monitorSignUpButton = page
|
||||
.locator("button.Button_primary___XZsP")
|
||||
.first();
|
||||
}
|
||||
|
||||
async signupWithMask(randomMask: string | null) {
|
||||
if (randomMask === null) {
|
||||
return new Error("Mask could not be created.");
|
||||
}
|
||||
|
||||
await this.page.goto("https://monitor.mozilla.org/", {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
await this.monitorSignUpInput.fill(randomMask as string);
|
||||
await this.monitorSignUpButton.click();
|
||||
await this.page.waitForURL("**/oauth/signup**");
|
||||
|
||||
await this.page
|
||||
.locator("#password")
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.page
|
||||
.locator("#vpassword")
|
||||
.fill(process.env.E2E_TEST_ACCOUNT_PASSWORD as string);
|
||||
await this.page.locator("#age").fill("31");
|
||||
await this.page.locator("#submit-btn").click();
|
||||
await this.page.waitForURL("**/confirm_signup_code**");
|
||||
|
||||
// verification email from fxa to generatedMaskEmail should be forwarded to E2E_TEST_ACCOUNT_FREE
|
||||
await getVerificationCode(
|
||||
process.env.E2E_TEST_ACCOUNT_FREE as string,
|
||||
this.page,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,29 +1,46 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
|
||||
export class SubscriptionPaymentPage {
|
||||
readonly page: Page
|
||||
readonly paypalButton: Locator
|
||||
readonly paymentDiv: Locator
|
||||
readonly productDetails: Locator
|
||||
readonly discountForm: Locator
|
||||
readonly paymentNameField: Locator
|
||||
readonly cardNumberField: Locator
|
||||
readonly cardExpiryField: Locator
|
||||
readonly cardCvcField: Locator
|
||||
readonly postalCodeField: Locator
|
||||
readonly authorizationCheckbox: Locator
|
||||
readonly page: Page;
|
||||
readonly paypalButton: Locator;
|
||||
readonly paymentDiv: Locator;
|
||||
readonly productDetails: Locator;
|
||||
readonly discountForm: Locator;
|
||||
readonly paymentNameField: Locator;
|
||||
readonly cardNumberField: Locator;
|
||||
readonly cardExpiryField: Locator;
|
||||
readonly cardCvcField: Locator;
|
||||
readonly postalCodeField: Locator;
|
||||
readonly authorizationCheckbox: Locator;
|
||||
readonly subscriptionTitle: Locator;
|
||||
readonly subscriptionType: Locator;
|
||||
readonly planDetails: Locator;
|
||||
readonly planType: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.authorizationCheckbox = page.locator('[data-testid="confirm"]')
|
||||
this.paypalButton = page.locator('[data-testid="pay-with-other"]')
|
||||
this.paymentDiv = page.locator('[data-testid="subscription-create"]')
|
||||
this.productDetails = page.locator('.plan-details-component-inner')
|
||||
this.discountForm = page.locator('[data-testid="coupon-component"]')
|
||||
this.paymentNameField = page.locator('[data-testid="name"]')
|
||||
this.cardNumberField = page.locator('[data-elements-stable-field-name="cardNumber"]')
|
||||
this.cardExpiryField = page.locator('[data-elements-stable-field-name="cardExpiry"]')
|
||||
this.cardCvcField = page.locator('[data-elements-stable-field-name="cardCvc"]')
|
||||
this.postalCodeField = page.locator('[data-elements-stable-field-name="postalCode"]')
|
||||
}
|
||||
}
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.authorizationCheckbox = page.locator('[data-testid="confirm"]');
|
||||
this.paypalButton = page.locator('[data-testid="pay-with-other"]');
|
||||
this.paymentDiv = page.locator('[data-testid="subscription-create"]');
|
||||
this.productDetails = page.locator(".plan-details-component-inner");
|
||||
this.discountForm = page.locator('[data-testid="coupon-component"]');
|
||||
this.paymentNameField = page.locator('[data-testid="name"]');
|
||||
this.cardNumberField = page.locator(
|
||||
'[data-elements-stable-field-name="cardNumber"]',
|
||||
);
|
||||
this.cardExpiryField = page.locator(
|
||||
'[data-elements-stable-field-name="cardExpiry"]',
|
||||
);
|
||||
this.cardCvcField = page.locator(
|
||||
'[data-elements-stable-field-name="cardCvc"]',
|
||||
);
|
||||
this.postalCodeField = page.locator(
|
||||
'[data-elements-stable-field-name="postalCode"]',
|
||||
);
|
||||
this.subscriptionTitle = page.locator(
|
||||
'[data-testid="subscription-create-title"]',
|
||||
);
|
||||
this.planDetails = page.locator("#plan-details-product");
|
||||
this.planType = page.locator(".plan-details-description");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,177 @@
|
|||
import test, { expect } from '../fixtures/basePages'
|
||||
import { checkAuthState } from '../e2eTestUtils/helpers';
|
||||
import test, { expect } from "../fixtures/basePages";
|
||||
import { checkAuthState } from "../e2eTestUtils/helpers";
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
test.skip(({ browserName }) => browserName !== 'firefox', 'firefox only e2e!');
|
||||
test.fixme('Relay e2e function email forwarding', () => {
|
||||
// use stored authenticated state
|
||||
test.use({ storageState: 'state.json' })
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
test.describe("FxA auth, random mask generation, and email forwarding @health_check", () => {
|
||||
test.skip(
|
||||
process.env.E2E_TEST_ENV === "prod",
|
||||
"This test only works on stage/dev environments",
|
||||
);
|
||||
// use stored authenticated state
|
||||
test.use({ storageState: "state.json" });
|
||||
|
||||
test.beforeEach(async ({ dashboardPage }) => {
|
||||
await dashboardPage.sendMaskEmail()
|
||||
});
|
||||
|
||||
test('Check that the user can use the masks on websites and receive emails sent to the masks, C1553068, C1553065, C1811801', async ({
|
||||
dashboardPage,
|
||||
page
|
||||
}) => {
|
||||
// This tests creates a new Mozilla Account with a new mask, to have
|
||||
// the signup confirmation email show up in the forwarded email count.
|
||||
// This is a pretty slow process:
|
||||
test.slow()
|
||||
await expect(async () => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
const forwardedEmailCount = await dashboardPage.checkForwardedEmailCount()
|
||||
|
||||
expect(forwardedEmailCount).toEqual('1')
|
||||
}).toPass()
|
||||
})
|
||||
})
|
||||
|
||||
test.fixme('Relay e2e auth flows', () => {
|
||||
// pricing table is being updated -- will update this test afterwords
|
||||
test.beforeEach(async ({ landingPage }) => {
|
||||
await landingPage.open()
|
||||
test.beforeEach(async ({ dashboardPage, mozillaMonitorPage }) => {
|
||||
const randomMask = await dashboardPage.generateOneRandomMask();
|
||||
await mozillaMonitorPage.signupWithMask(randomMask);
|
||||
});
|
||||
|
||||
test('Verify that the "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage
|
||||
test("Check that the user can use the masks on websites and receive emails sent to the masks, C1553068, C1553065, C1811801", async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
await landingPage.selectPricingPlanSignUp()
|
||||
// This tests creates a new Mozilla Account with a new mask, to have
|
||||
// the signup confirmation email show up in the forwarded email count.
|
||||
// This is a pretty slow process:
|
||||
|
||||
await expect(async () => {
|
||||
await dashboardPage.open();
|
||||
await checkAuthState(page);
|
||||
await dashboardPage.skipOnboarding();
|
||||
const forwardedEmailCount =
|
||||
await dashboardPage.checkForwardedEmailCount();
|
||||
|
||||
expect(forwardedEmailCount).toEqual("1");
|
||||
}).toPass();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Email forwarding and trackers removal", () => {
|
||||
test.skip(
|
||||
({ browserName }) => browserName !== "firefox",
|
||||
"firefox only test",
|
||||
);
|
||||
test.use({ storageState: "state.json" });
|
||||
let initialTrackersCount;
|
||||
|
||||
test.beforeEach(async ({ dashboardPage }) => {
|
||||
const randomMask = await dashboardPage.generateOneRandomMask();
|
||||
initialTrackersCount =
|
||||
await dashboardPage.maskCardTrackersCount.textContent();
|
||||
// Set trackers removed option
|
||||
await dashboardPage.setTrackersRemovalOpt();
|
||||
try {
|
||||
await dashboardPage.signUpForPageWithTrackers(randomMask as string);
|
||||
} catch (e) {
|
||||
test.skip(true, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
test("Verify that an email with trackers is forwarded, and trackers are detected", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
const newTrackersCount = await dashboardPage.checkTrackersCount();
|
||||
expect(parseInt(newTrackersCount)).toBeGreaterThan(
|
||||
parseInt(initialTrackersCount),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Subscription flows @health_check", () => {
|
||||
/**
|
||||
* Verifies that all plans correctly redirect to its corresponding subscriptions page.
|
||||
*/
|
||||
|
||||
test.beforeEach(async ({ landingPage }) => {
|
||||
await landingPage.open();
|
||||
});
|
||||
|
||||
test('Verify that the yearly emails plan "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage,
|
||||
subscriptionPage,
|
||||
}) => {
|
||||
await landingPage.selectYearlyEmailsPlan();
|
||||
const expectedPlanDetails =
|
||||
process.env["E2E_TEST_ENV"] === "prod"
|
||||
? "Relay Premium"
|
||||
: "Relay Premium (stage)";
|
||||
|
||||
// verify redirect to subscription page
|
||||
expect(await landingPage.subscriptionTitle.textContent()).toContain('Set up your subscription')
|
||||
})
|
||||
})
|
||||
expect(await subscriptionPage.subscriptionTitle.textContent()).toContain(
|
||||
"Set up your subscription",
|
||||
);
|
||||
expect(await subscriptionPage.planDetails.textContent()).toEqual(
|
||||
expectedPlanDetails,
|
||||
);
|
||||
expect(await subscriptionPage.planType.textContent()).toContain("yearly");
|
||||
});
|
||||
|
||||
test('Verify that the monthly emails plan "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage,
|
||||
subscriptionPage,
|
||||
}) => {
|
||||
await landingPage.selectMonthlyEmailsPlan();
|
||||
const expectedPlanDetails =
|
||||
process.env["E2E_TEST_ENV"] === "prod"
|
||||
? "Relay Premium"
|
||||
: "Relay Premium (stage)";
|
||||
|
||||
// verify redirect to subscription page
|
||||
expect(await subscriptionPage.subscriptionTitle.textContent()).toContain(
|
||||
"Set up your subscription",
|
||||
);
|
||||
expect(await subscriptionPage.planDetails.textContent()).toEqual(
|
||||
expectedPlanDetails,
|
||||
);
|
||||
expect(await subscriptionPage.planType.textContent()).toContain("monthly");
|
||||
});
|
||||
|
||||
test('Verify that the yearly emails and phones bundle plan "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage,
|
||||
subscriptionPage,
|
||||
}) => {
|
||||
await landingPage.selectYearlyPhonesEmailsBundle();
|
||||
const expectedPlanDetails =
|
||||
process.env["E2E_TEST_ENV"] === "prod"
|
||||
? "Relay Premium: Phone Number & Email Address Masking"
|
||||
: "Relay Email & Phone Protection (stage)";
|
||||
|
||||
// verify redirect to subscription page
|
||||
expect(await subscriptionPage.subscriptionTitle.textContent()).toContain(
|
||||
"Set up your subscription",
|
||||
);
|
||||
expect(await subscriptionPage.planDetails.textContent()).toEqual(
|
||||
expectedPlanDetails,
|
||||
);
|
||||
expect(await subscriptionPage.planType.textContent()).toContain("yearly");
|
||||
});
|
||||
|
||||
test('Verify that the monthly emails and phones bundle plan "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage,
|
||||
subscriptionPage,
|
||||
}) => {
|
||||
await landingPage.selectMonthlyPhonesEmailsBundle();
|
||||
const expectedPlanDetails =
|
||||
process.env["E2E_TEST_ENV"] === "prod"
|
||||
? "Relay Premium: Phone Number & Email Address Masking"
|
||||
: "Relay Email & Phone Protection (stage)";
|
||||
|
||||
// verify redirect to subscription page
|
||||
expect(await subscriptionPage.subscriptionTitle.textContent()).toContain(
|
||||
"Set up your subscription",
|
||||
);
|
||||
expect(await subscriptionPage.planDetails.textContent()).toEqual(
|
||||
expectedPlanDetails,
|
||||
);
|
||||
expect(await subscriptionPage.planType.textContent()).toContain("monthly");
|
||||
});
|
||||
|
||||
test('Verify that the VPN bundle "Sign Up" button works correctly, C1818792', async ({
|
||||
landingPage,
|
||||
subscriptionPage,
|
||||
}) => {
|
||||
await landingPage.selectVpnBundlePlan();
|
||||
const expectedPlanDetails =
|
||||
process.env["E2E_TEST_ENV"] === "prod"
|
||||
? "Mozilla VPN & Firefox Relay"
|
||||
: "Firefox Relay & Mozilla VPN (Stage)";
|
||||
|
||||
// verify redirect to subscription page
|
||||
expect(await subscriptionPage.subscriptionTitle.textContent()).toContain(
|
||||
"Set up your subscription",
|
||||
);
|
||||
expect(await subscriptionPage.planDetails.textContent()).toEqual(
|
||||
expectedPlanDetails,
|
||||
);
|
||||
expect(await subscriptionPage.planType.textContent()).toContain("yearly");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,85 +1,105 @@
|
|||
import test, { expect } from '../fixtures/basePages'
|
||||
import { checkAuthState, defaultScreenshotOpts } from '../e2eTestUtils/helpers';
|
||||
import test, { expect } from "../fixtures/basePages";
|
||||
import { checkAuthState, defaultScreenshotOpts } from "../e2eTestUtils/helpers";
|
||||
|
||||
// using logged in state outside of describe block will cover state for all tests in file
|
||||
test.use({ storageState: 'state.json' })
|
||||
test.describe.fixme('Free - General Functionalities, Desktop', () => {
|
||||
test.use({ storageState: "state.json" });
|
||||
test.describe("Free - General Functionalities, Desktop", () => {
|
||||
test.beforeEach(async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
await dashboardPage.maybeDeleteMasks()
|
||||
await dashboardPage.open();
|
||||
await checkAuthState(page);
|
||||
await dashboardPage.skipOnboarding();
|
||||
await dashboardPage.maybeDeleteMasks();
|
||||
});
|
||||
|
||||
test('Check the free user can only create 5 masks, C1553067', async ({ dashboardPage, page }) => {
|
||||
test("Check the free user can only create 5 masks, C1553067 @health_check", async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
// Generating five masks takes a while:
|
||||
test.slow()
|
||||
await expect(async () => {
|
||||
await dashboardPage.generateMask(5)
|
||||
expect(await page.locator(dashboardPage.maskCardString).count() === 5)
|
||||
}).toPass()
|
||||
await dashboardPage.generateMask(5);
|
||||
expect((await page.locator(dashboardPage.maskCardString).count()) === 5);
|
||||
}).toPass();
|
||||
|
||||
// After five times, user cannot add other masks anymore
|
||||
expect(await dashboardPage.maxMaskLimitButton.textContent()).toContain(
|
||||
"Get unlimited email masks",
|
||||
);
|
||||
expect(await dashboardPage.maxMaskBannerText.innerText()).toContain(
|
||||
"mask limit",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// After five times, the button becomes greyed-out and the user cannot add other masks anymore (TODO: for a free user from a country where Premium is NOT available).
|
||||
expect(await dashboardPage.maxMaskLimitButton.textContent()).toContain('Get unlimited email masks')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe.fixme('Free - General Functionalities, Desktop - Visual Regression', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'firefox', 'firefox only image comparisons!');
|
||||
test.describe("Free - General Functionalities, Desktop - Visual Regression", () => {
|
||||
test.skip(
|
||||
({ browserName }) => browserName !== "firefox",
|
||||
"firefox only image comparisons!",
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
await dashboardPage.maybeDeleteMasks()
|
||||
await dashboardPage.open();
|
||||
await checkAuthState(page);
|
||||
await dashboardPage.skipOnboarding();
|
||||
await dashboardPage.maybeDeleteMasks();
|
||||
});
|
||||
|
||||
test('Verify that the Header is displayed correctly for a Free user that is logged in, C1812639', async ({ dashboardPage }) => {
|
||||
await dashboardPage.maybeCloseToaster()
|
||||
test("Verify that the Header is displayed correctly for a Free user that is logged in, C1812639", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
await expect(dashboardPage.header).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-dashboardHeader.png`,
|
||||
{...defaultScreenshotOpts, mask: [dashboardPage.userMenuLetter] }
|
||||
{ ...defaultScreenshotOpts },
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
test.fixme('Verify that the "Profile" button and its options work correctly, C1812641', async ({ dashboardPage }) => {
|
||||
await dashboardPage.relayExtensionBanner.scrollIntoViewIfNeeded()
|
||||
test("Verify that the extension upgrade banner is shown, C1812641", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
await dashboardPage.relayExtensionBanner.scrollIntoViewIfNeeded();
|
||||
await expect(dashboardPage.relayExtensionBanner).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-relayExtensionBanner.png`,
|
||||
defaultScreenshotOpts
|
||||
defaultScreenshotOpts,
|
||||
);
|
||||
});
|
||||
|
||||
await dashboardPage.bottomUgradeBanner.scrollIntoViewIfNeeded()
|
||||
await expect(dashboardPage.bottomUgradeBanner).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-bottomUgradeBanner.png`,
|
||||
defaultScreenshotOpts
|
||||
test("Verify that opened mask cards are displayed correctly to a Free user, C1553070", async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
await expect(async () => {
|
||||
await dashboardPage.generateMask(1);
|
||||
expect((await page.locator(dashboardPage.maskCardString).count()) === 1);
|
||||
}).toPass();
|
||||
|
||||
await expect(page.locator(dashboardPage.maskCardString)).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-maskCard.png`,
|
||||
{
|
||||
...defaultScreenshotOpts,
|
||||
mask: [
|
||||
dashboardPage.maskCardGeneratedEmail,
|
||||
dashboardPage.maskCardBottomMeta,
|
||||
],
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
test('Verify that opened mask cards are displayed correctly to a Free user, C1553070', async ({ dashboardPage, page }) => {
|
||||
test("Check that the user can delete an mask, and is prompted to confirm before they delete, C1553071 @health_check", async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
await expect(async () => {
|
||||
await dashboardPage.generateMask(1)
|
||||
expect(await page.locator(dashboardPage.maskCardString).count() === 1)
|
||||
}).toPass()
|
||||
await dashboardPage.generateMask(1);
|
||||
expect((await page.locator(dashboardPage.maskCardString).count()) === 1);
|
||||
await dashboardPage.maskCardDeleteButton.click();
|
||||
}).toPass();
|
||||
|
||||
await expect(page.locator(dashboardPage.maskCardString)).toHaveScreenshot(`${process.env.E2E_TEST_ENV}-maskCard.png`,
|
||||
{...defaultScreenshotOpts, mask: [
|
||||
dashboardPage.maskCardForwardEmail,
|
||||
dashboardPage.maskCardGeneratedEmail,
|
||||
dashboardPage.maskCardCreatedDate
|
||||
]});
|
||||
})
|
||||
|
||||
test.skip('Check that the user can delete an mask, and is prompted to confirm before they delete, C1553071', async ({ dashboardPage, page }) => {
|
||||
await expect(async () => {
|
||||
await dashboardPage.generateMask(1)
|
||||
expect(await page.locator(dashboardPage.maskCardString).count() === 1)
|
||||
await dashboardPage.maskCardDeleteButton.click()
|
||||
}).toPass()
|
||||
|
||||
await expect(dashboardPage.maskCardDeleteDialogModal).toHaveScreenshot(`${process.env.E2E_TEST_ENV}-maskCardDeleteDialogModal.png`,
|
||||
{...defaultScreenshotOpts, mask: [
|
||||
dashboardPage.maskCardDeleteDialogModalEmailString,
|
||||
dashboardPage.maskCardDeleteDialogModalGeneratedEmail
|
||||
]});
|
||||
})
|
||||
})
|
||||
await expect(dashboardPage.maskCardDeleteDialogModal).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-maskCardDeleteDialogModal.png`,
|
||||
{
|
||||
...defaultScreenshotOpts,
|
||||
mask: [dashboardPage.maskCardDeleteDialogModalGeneratedEmail],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
До Ширина: | Высота: | Размер: 48 KiB |
До Ширина: | Высота: | Размер: 47 KiB |
До Ширина: | Высота: | Размер: 14 KiB |
До Ширина: | Высота: | Размер: 38 KiB |
До Ширина: | Высота: | Размер: 37 KiB После Ширина: | Высота: | Размер: 36 KiB |
До Ширина: | Высота: | Размер: 42 KiB |
До Ширина: | Высота: | Размер: 39 KiB После Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 17 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 21 KiB |
До Ширина: | Высота: | Размер: 48 KiB |
До Ширина: | Высота: | Размер: 47 KiB |
До Ширина: | Высота: | Размер: 13 KiB |
До Ширина: | Высота: | Размер: 36 KiB |
До Ширина: | Высота: | Размер: 35 KiB После Ширина: | Высота: | Размер: 36 KiB |
До Ширина: | Высота: | Размер: 46 KiB |
До Ширина: | Высота: | Размер: 43 KiB После Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 22 KiB |
До Ширина: | Высота: | Размер: 22 KiB После Ширина: | Высота: | Размер: 21 KiB |
До Ширина: | Высота: | Размер: 15 KiB |
До Ширина: | Высота: | Размер: 48 KiB |
До Ширина: | Высота: | Размер: 47 KiB |
До Ширина: | Высота: | Размер: 15 KiB |
До Ширина: | Высота: | Размер: 53 KiB |
Двоичные данные
e2e-tests/specs/relay-general-functionality.spec.ts-snapshots/stage-maskCard-firefox-linux.png
Normal file
После Ширина: | Высота: | Размер: 36 KiB |
До Ширина: | Высота: | Размер: 46 KiB |
До Ширина: | Высота: | Размер: 44 KiB После Ширина: | Высота: | Размер: 28 KiB |
До Ширина: | Высота: | Размер: 61 KiB |
До Ширина: | Высота: | Размер: 16 KiB После Ширина: | Высота: | Размер: 21 KiB |
До Ширина: | Высота: | Размер: 16 KiB |
|
@ -1,49 +1,67 @@
|
|||
import test, { expect } from '../fixtures/basePages'
|
||||
import { defaultScreenshotOpts } from '../e2eTestUtils/helpers';
|
||||
import test, { expect } from "../fixtures/basePages";
|
||||
import { defaultScreenshotOpts } from "../e2eTestUtils/helpers";
|
||||
|
||||
test.describe('Firefox Relay - Landing Page - Visual Regression', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'firefox', 'firefox only image comparisons!');
|
||||
test.describe("Firefox Relay - Landing Page - Visual Regression @health_check", () => {
|
||||
test.skip(
|
||||
({ browserName }) => browserName !== "firefox",
|
||||
"firefox only image comparisons!",
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ landingPage }) => {
|
||||
await landingPage.open()
|
||||
await landingPage.open();
|
||||
});
|
||||
|
||||
test('Verify that the header is displayed correctly for a user that is NOT logged in, C1812637', async ({ landingPage }) => {
|
||||
test("Verify that the header is displayed correctly for a user that is NOT logged in, C1812637", async ({
|
||||
landingPage,
|
||||
}) => {
|
||||
await expect(landingPage.header).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-landingHeader.png`,
|
||||
defaultScreenshotOpts
|
||||
defaultScreenshotOpts,
|
||||
);
|
||||
});
|
||||
|
||||
test("Verify home page is not malformed", async ({ landingPage }) => {
|
||||
await expect(landingPage.heroSection).toHaveScreenshot(
|
||||
`${process.env.E2E_TEST_ENV}-landingHero.png`,
|
||||
defaultScreenshotOpts,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test.describe('Check header buttons and their redirects, C1812638', () => {
|
||||
test.describe("Check header buttons and their redirects, C1812638 @health_check", () => {
|
||||
test.beforeEach(async ({ landingPage }) => {
|
||||
await landingPage.open()
|
||||
await landingPage.open();
|
||||
});
|
||||
|
||||
test('Verify home FAQ button redirect', async ({ landingPage }) => {
|
||||
const FAQRedirectLink = await landingPage.FAQButton.getAttribute('href')
|
||||
expect(FAQRedirectLink).toEqual('/faq/')
|
||||
})
|
||||
test("Verify home FAQ button redirect", async ({ landingPage }) => {
|
||||
const FAQRedirectLink = await landingPage.FAQButton.getAttribute("href");
|
||||
expect(FAQRedirectLink).toEqual("/faq/");
|
||||
});
|
||||
|
||||
test('Verify home button redirect', async ({ landingPage }) => {
|
||||
const homeRedirectLink = await landingPage.homeButton.getAttribute('href')
|
||||
expect(homeRedirectLink).toEqual('/')
|
||||
})
|
||||
test("Verify home button redirect", async ({ landingPage }) => {
|
||||
const homeRedirectLink = await landingPage.homeButton.getAttribute("href");
|
||||
expect(homeRedirectLink).toEqual("/");
|
||||
});
|
||||
|
||||
test('Verify home firefox logo redirect', async ({ landingPage }) => {
|
||||
const firefoxLogoRedirectLink = await landingPage.firefoxLogo.getAttribute('href')
|
||||
expect(firefoxLogoRedirectLink).toEqual('/')
|
||||
})
|
||||
test("Verify home firefox logo redirect", async ({ landingPage }) => {
|
||||
const firefoxLogoRedirectLink =
|
||||
await landingPage.firefoxLogo.getAttribute("href");
|
||||
expect(firefoxLogoRedirectLink).toEqual("/");
|
||||
});
|
||||
|
||||
test('Verify sign in button authentication flow, C1818784', async ({ landingPage, authPage }) => {
|
||||
await landingPage.goToSignIn()
|
||||
expect(authPage.emailInputField.isVisible()).toBeTruthy()
|
||||
})
|
||||
test("Verify sign in button authentication flow, C1818784", async ({
|
||||
landingPage,
|
||||
authPage,
|
||||
}) => {
|
||||
await landingPage.goToSignIn();
|
||||
expect(authPage.emailInputField.isVisible()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Verify sign up button authentication flow, C1818782', async ({ landingPage, authPage }) => {
|
||||
await landingPage.goToSignUp()
|
||||
expect(authPage.emailInputField.isVisible()).toBeTruthy()
|
||||
})
|
||||
});
|
||||
test("Verify sign up button authentication flow, C1818782", async ({
|
||||
landingPage,
|
||||
authPage,
|
||||
}) => {
|
||||
await landingPage.goToSignUp();
|
||||
expect(authPage.emailInputField.isVisible()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 30 KiB |
До Ширина: | Высота: | Размер: 10 KiB |
Двоичные данные
e2e-tests/specs/relay-home-page.spec.ts-snapshots/dev-landingHero-firefox-linux.png
Normal file
После Ширина: | Высота: | Размер: 113 KiB |
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 30 KiB |
До Ширина: | Высота: | Размер: 10 KiB |
Двоичные данные
e2e-tests/specs/relay-home-page.spec.ts-snapshots/prod-landingHero-firefox-linux.png
Normal file
После Ширина: | Высота: | Размер: 113 KiB |
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 30 KiB |
До Ширина: | Высота: | Размер: 12 KiB |
Двоичные данные
e2e-tests/specs/relay-home-page.spec.ts-snapshots/stage-landingHero-firefox-linux.png
Normal file
После Ширина: | Высота: | Размер: 113 KiB |
|
@ -1,20 +1,50 @@
|
|||
import test, { expect } from '../fixtures/basePages'
|
||||
import test, { expect } from "../fixtures/basePages";
|
||||
|
||||
test.describe.skip('Premium - General Functionalities, Desktop', () => {
|
||||
test.describe("Premium - General Functionalities, Desktop", () => {
|
||||
test.beforeEach(async ({ landingPage, authPage, dashboardPage }) => {
|
||||
await landingPage.open()
|
||||
await landingPage.goToSignIn()
|
||||
await authPage.login(process.env.E2E_TEST_ACCOUNT_PREMIUM as string)
|
||||
await dashboardPage.maybeDeleteMasks()
|
||||
await landingPage.open();
|
||||
await landingPage.goToSignIn();
|
||||
await authPage.login(process.env.E2E_TEST_ACCOUNT_PREMIUM as string);
|
||||
const totalMasks = await dashboardPage.emailMasksUsedAmount.textContent();
|
||||
await dashboardPage.maybeDeleteMasks(true, parseInt(totalMasks as string));
|
||||
});
|
||||
|
||||
test('Check the premium user can more than 5 masks', async ({ dashboardPage }) => {
|
||||
await dashboardPage.generateMask(6)
|
||||
|
||||
await expect.poll(async () => {
|
||||
return await dashboardPage.emailMasksUsedAmount.textContent()
|
||||
}, {
|
||||
intervals: [1_000]
|
||||
}).toContain('6');
|
||||
})
|
||||
})
|
||||
test("Verify that a premium user can make more than 5 masks @health_check", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
await dashboardPage.generateMask(6, true);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
return await dashboardPage.emailMasksUsedAmount.textContent();
|
||||
},
|
||||
{
|
||||
intervals: [1_000],
|
||||
},
|
||||
)
|
||||
.toContain("6");
|
||||
});
|
||||
|
||||
test("Verify that a user can click the mask blocking options", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
await dashboardPage.generateMask(1, true);
|
||||
await dashboardPage.blockPromotions.click();
|
||||
expect(await dashboardPage.blockLevelPromosLabel.textContent()).toContain(
|
||||
"Blocking promo emails",
|
||||
);
|
||||
await dashboardPage.blockAll.click();
|
||||
expect(await dashboardPage.blockLevelAllLabel.textContent()).toContain(
|
||||
"Blocking all emails",
|
||||
);
|
||||
});
|
||||
|
||||
test("Verify that a premium user can generate a custom mask @health_check", async ({
|
||||
dashboardPage,
|
||||
}) => {
|
||||
// When there are zero masks, a random mask must be generated first
|
||||
await dashboardPage.generateMask();
|
||||
await dashboardPage.generatePremiumDomainMask();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,51 +1,36 @@
|
|||
import test, { expect } from '../fixtures/basePages'
|
||||
import { checkAuthState, defaultScreenshotOpts } from '../e2eTestUtils/helpers';
|
||||
import test, { expect } from "../fixtures/basePages";
|
||||
import { checkAuthState, defaultScreenshotOpts } from "../e2eTestUtils/helpers";
|
||||
|
||||
// using logged in state outside of describe block will cover state for all tests in file
|
||||
test.use({ storageState: 'state.json' })
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
test.describe('Premium Relay - Purchase Premium Flow, Desktop', () => {
|
||||
test.skip(({ browserName }) => browserName === 'firefox', 'FxA Issue with with authentication');
|
||||
|
||||
test.use({ storageState: "state.json" });
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
test.describe("Premium Relay - Purchase Premium Flow, Desktop", () => {
|
||||
test.beforeEach(async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
await dashboardPage.open();
|
||||
await checkAuthState(page);
|
||||
});
|
||||
|
||||
test('Verify that the "Upgrade" button redirects correctly, C1812640, 1808503', async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.upgrade()
|
||||
expect(page.url()).toContain('/premium/')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe.skip(() => {
|
||||
// TODO: add flow for stage only
|
||||
// run this test only on stage as only stage will accept test cards
|
||||
test.skip(() => process.env.E2E_TEST_ENV !== 'stage', 'run only on stage');
|
||||
|
||||
test.beforeEach(async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
test('Verify that the "Upgrade" button redirects correctly, C1812640, 1808503', async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
await dashboardPage.upgrade();
|
||||
expect(page.url()).toContain("/premium/");
|
||||
});
|
||||
})
|
||||
|
||||
test.describe.skip('Premium Relay - Purchase Premium Flow, Desktop - Visual Regression', () => {
|
||||
test.skip(({ browserName }) => browserName !== 'firefox', 'firefox only image comparisons!');
|
||||
});
|
||||
|
||||
test.describe("Premium Relay - Purchase Premium Flow, Desktop - Visual Regression", () => {
|
||||
test.beforeEach(async ({ dashboardPage, page }) => {
|
||||
await dashboardPage.open()
|
||||
await checkAuthState(page)
|
||||
await dashboardPage.open();
|
||||
await checkAuthState(page);
|
||||
await dashboardPage.skipOnboarding();
|
||||
});
|
||||
|
||||
test('Verify that the subscription page is displayed correctly, C1553108', async ({ subscriptionPage, dashboardPage, page }) => {
|
||||
await dashboardPage.upgradeNow()
|
||||
expect(page.url()).toContain('subscriptions')
|
||||
|
||||
await expect(subscriptionPage.paypalButton).toBeVisible()
|
||||
// will uncomment in a fast follow update
|
||||
// await expect(subscriptionPage.productDetails).toHaveScreenshot(
|
||||
// `${process.env.E2E_TEST_ENV}-productDetails.png`,
|
||||
// defaultScreenshotOpts
|
||||
// );
|
||||
})
|
||||
})
|
||||
test("Verify that the subscription page is displayed correctly, C1553108", async ({
|
||||
dashboardPage,
|
||||
page,
|
||||
}) => {
|
||||
await dashboardPage.upgradeNow();
|
||||
expect(page.url()).toContain("premium");
|
||||
});
|
||||
});
|
||||
|
|
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 32 KiB |
До Ширина: | Высота: | Размер: 23 KiB |
До Ширина: | Высота: | Размер: 22 KiB |
|
@ -15,14 +15,14 @@ const config: PlaywrightTestConfig = {
|
|||
/* Add location of specs. */
|
||||
testDir: 'e2e-tests/specs',
|
||||
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 60_000,
|
||||
/* Maximum time one test can run for. 3 minutes */
|
||||
timeout: 60 * 1000,
|
||||
|
||||
/* Global setup */
|
||||
globalSetup: require.resolve('./e2e-tests/global-setup.ts'),
|
||||
|
||||
/* Max time in milliseconds the whole test suite can to prevent CI breaking. */
|
||||
globalTimeout: 360_000,
|
||||
/* Max time in milliseconds the whole test suite can to prevent CI breaking. 15 minutes */
|
||||
globalTimeout: 60 * 15 * 1000,
|
||||
|
||||
// adding missing snapshots for later comparison
|
||||
updateSnapshots: 'missing',
|
||||
|
@ -35,7 +35,7 @@ const config: PlaywrightTestConfig = {
|
|||
timeout: 5_000
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
// fullyParallel: true,
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
|
|