Skip to content

Commit

Permalink
Add basic test for sign in/out
Browse files Browse the repository at this point in the history
  • Loading branch information
rwood-moz committed Dec 11, 2024
1 parent aa7a69c commit cbb67d5
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 12 deletions.
8 changes: 5 additions & 3 deletions frontend/src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ const onEnter = () => {
</div>
<div class="form-body">
<form v-if="loginStep !== LoginSteps.SignUpConfirm" class="form" ref="formRef" autocomplete="off" @submit.prevent @keyup.enter="() => onEnter()">
<text-input name="email" v-model="email" :required="true">{{ t('login.form.email') }}</text-input>
<text-input v-if="isPasswordAuth" name="password" v-model="password" :required="true" type="password">{{ t('label.password') }}</text-input>
<text-input v-if="loginStep === LoginSteps.SignUp && !hideInviteField" name="inviteCode" v-model="inviteCode" :help="t('login.form.no-invite-code')">{{ t('label.inviteCode') }}</text-input>
<text-input name="email" v-model="email" :required="true" data-testid="login-email-input">{{ t('login.form.email') }}</text-input>
<text-input v-if="isPasswordAuth" name="password" v-model="password" :required="true" type="password" data-testid="login-password-input">{{ t('label.password') }}</text-input>
<text-input v-if="loginStep === LoginSteps.SignUp && !hideInviteField" name="inviteCode" v-model="inviteCode" :help="t('login.form.no-invite-code')" data-testid="login-invite-code-input">{{ t('label.inviteCode') }}</text-input>
</form>
</div>
<template v-slot:actions>
Expand All @@ -226,6 +226,7 @@ const onEnter = () => {
:disabled="isLoading"
@click="onEnter()"
v-if="loginStep !== LoginSteps.SignUpConfirm"
data-testid="login-continue-btn"
>
{{ t('label.continue') }}
</primary-button>
Expand All @@ -235,6 +236,7 @@ const onEnter = () => {
:disabled="isLoading"
@click="router.push({name: 'home'})"
v-else
data-testid="login-close-btn"
>
{{ t('label.close') }}
</primary-button>
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Appointment E2E Test Configuration

# Production sign-in (FxA) credentials
APPT_PROD_LOGIN_EMAIL=
APPT_PROD_LOGIN_PWORD=
1 change: 1 addition & 0 deletions test/e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
node_modules/
/test-results/
/playwright-report/
Expand Down
1 change: 1 addition & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Guide for running the E2E tests.
## Installation

todo
include npm install . etc

```bash
todo
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/const/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ export const APPT_PROD_URL = 'https://appointment.day/';

// page titles
export const APPT_PAGE_TITLE = 'Thunderbird Appointment';
export const FXA_PAGE_TITLE = 'Mozilla accounts';

// production sign-in credentials
export const PROD_LOGIN_EMAIL = process.env.APPT_PROD_LOGIN_EMAIL;
export const PROD_LOGIN_PWORD = process.env.APPT_PROD_LOGIN_PWORD;
16 changes: 15 additions & 1 deletion test/e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion test/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"description": "",
"devDependencies": {
"@playwright/test": "^1.49.0",
"@types/node": "^22.10.1"
"@types/node": "^22.10.1",
"dotenv": "^16.3.1"
}
}
17 changes: 17 additions & 0 deletions test/e2e/pages/dashboard-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect, type Page, type Locator } from '@playwright/test';

export class DashboardPage {
readonly page: Page;
readonly navBarDashboardBtn: Locator;
readonly userMenuAvatar: Locator;

constructor(page: Page) {
this.page = page;
// temporary; update after next deployment when my test-dataid's are deployed
//this.navBarDashboardBtn = this.page.getByTestId('nav-bar-dashboard-button');
this.navBarDashboardBtn = this.page.getByRole('link', { name: 'Dashboard' });
// temporary; update after next deployment when my test-dataid's are deployed
//this.userMenuAvatar = this.page.getByTestId('user-menu-avatar');
this.userMenuAvatar = this.page.locator('.flex-center');
}
}
24 changes: 24 additions & 0 deletions test/e2e/pages/fxa-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, type Page, type Locator } from '@playwright/test';
import { PROD_LOGIN_PWORD } from '../const/constants';

export class FxAPage {
readonly page: Page;
readonly signInHeaderText: Locator;
readonly userAvatar: Locator;
readonly passwordInput: Locator;
readonly signInButton: Locator;

constructor(page: Page) {
this.page = page;
this.signInHeaderText = this.page.getByText('Enter your password');
this.userAvatar = this.page.getByTestId('avatar-default');
this.passwordInput = this.page.getByRole('textbox', {name: 'password' });
this.signInButton = this.page.getByRole('button', { name: 'Sign in' });
}

async signIn() {
expect(PROD_LOGIN_PWORD, 'getting APPT_PROD_LOGIN_PWORD env var').toBeTruthy();
await this.passwordInput.fill(String(PROD_LOGIN_PWORD));
await this.signInButton.click();
}
}
41 changes: 37 additions & 4 deletions test/e2e/pages/splashscreen-page.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
import { type Page, type Locator } from '@playwright/test';
import { APPT_PROD_URL } from '../const/constants';
import { expect, type Page, type Locator } from '@playwright/test';
import { APPT_PROD_URL, PROD_LOGIN_EMAIL, FXA_PAGE_TITLE } from '../const/constants';

export class SplashscreenPage {
readonly page: Page;
readonly loginBtn: Locator;
readonly signUpBetaBtn: Locator;
readonly loginEmailInput: Locator;
readonly loginContinueBtn: Locator;

constructor(page: Page) {
this.page = page;
this.loginBtn = this.page.getByTestId('home-login-btn');
this.signUpBetaBtn = this.page.getByTestId('home-sign-up-beta-btn');
//update this after data-testid 's are deployed to prod
//this.loginBtn = this.page.getByTestId('home-login-btn');
this.loginBtn = this.page.getByTitle('Log in');
//update this after data-testid 's are deployed to prod
//this.signUpBetaBtn = this.page.getByTestId('home-sign-up-beta-btn');
this.signUpBetaBtn = this.page.getByTitle('Sign up for the beta');
//update this after data-testid 's are deployed to prod
//this.loginEmailInput = this.page.getByTestId('login-email-input');
this.loginEmailInput = this.page.getByLabel('Email address');
//update this after data-testid 's are deployed to prod
//this.loginContinueBtn = this.page.getByTestId('login-continue-btn');
this.loginContinueBtn = this.page.getByTitle('Continue');
}

async gotoProd() {
await this.page.goto(APPT_PROD_URL);
}

async clickLoginBtn() {
await this.loginBtn.click();
}

async enterLoginEmail(emailAddress: string) {
await this.loginEmailInput.fill(emailAddress);
}

async clickLoginContinueBtn() {
await this.loginContinueBtn.click();
}

async getToFxA() {
await expect(this.loginBtn).toBeVisible();
await this.clickLoginBtn();
expect(PROD_LOGIN_EMAIL, 'getting APPT_PROD_LOGIN_EMAIL env var').toBeTruthy();
await this.enterLoginEmail(String(PROD_LOGIN_EMAIL))
await this.clickLoginContinueBtn();
await expect(this.page).toHaveTitle(FXA_PAGE_TITLE, { timeout: 30_000 }); // be generous in case FxA is slow to load
}
}
6 changes: 3 additions & 3 deletions test/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { defineConfig, devices } from '@playwright/test';
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
import dotenv from 'dotenv';
import path from 'path';
dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
Expand Down
44 changes: 44 additions & 0 deletions test/e2e/tests/sign-in.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';
import { SplashscreenPage } from '../pages/splashscreen-page';
import { FxAPage } from '../pages/fxa-page';
import { FXA_PAGE_TITLE, APPT_PAGE_TITLE } from '../const/constants';
import { DashboardPage } from '../pages/dashboard-page';

let splashscreen: SplashscreenPage;
let fxa_sign_in: FxAPage;
let dashboard_page: DashboardPage;

test.beforeEach(async ({ page }) => {
// navigate to the main appointment page (splashscreen)
splashscreen = new SplashscreenPage(page);
fxa_sign_in = new FxAPage(page);
dashboard_page = new DashboardPage(page);
await splashscreen.gotoProd();
});

// verify basic sign-in flow works
test.describe('basic sign-in flow', {
tag: '@prod-sanity'
}, () => {
test('clicking login button brings up the sign-in dialog', async ({ page }) => {
await expect(splashscreen.loginBtn).toBeVisible();
await splashscreen.clickLoginBtn();
await expect(splashscreen.loginEmailInput).toBeVisible();
await expect(splashscreen.loginContinueBtn).toBeVisible();
});

test('clicking continue button on login dialog brings up the fxa sign-in dialog', async ({ page }) => {
await splashscreen.getToFxA();
await expect(fxa_sign_in.userAvatar).toBeVisible({ timeout: 30_000}); // generous time for fxa to appear
await expect(fxa_sign_in.signInButton).toBeVisible();
});

test('able to sign-in to appointment via fxa', async ({ page }) => {
await splashscreen.getToFxA();
await expect(fxa_sign_in.signInHeaderText).toBeVisible({ timeout: 30_000 });
await fxa_sign_in.signIn();
await expect(page).toHaveTitle(APPT_PAGE_TITLE, { timeout: 30_000 }); // give generous time for fxa sign to complete
await expect(dashboard_page.userMenuAvatar).toBeVisible({ timeout: 30_000 });
await expect(dashboard_page.navBarDashboardBtn).toBeVisible({ timeout: 30_000 });
});
});
30 changes: 30 additions & 0 deletions test/e2e/tests/sign-out.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { test, expect } from '@playwright/test';
import { SplashscreenPage } from '../pages/splashscreen-page';
import { FxAPage } from '../pages/fxa-page';
import { DashboardPage } from '../pages/dashboard-page';

let splashscreen: SplashscreenPage;
let fxa_sign_in: FxAPage;
let dashboard_page: DashboardPage;

test.beforeEach(async ({ page }) => {
// navigate to the main appointment page (splashscreen) and sign-in via fxa
splashscreen = new SplashscreenPage(page);
fxa_sign_in = new FxAPage(page);
dashboard_page = new DashboardPage(page);
await splashscreen.gotoProd();
await splashscreen.getToFxA();
await expect(fxa_sign_in.signInHeaderText).toBeVisible({ timeout: 30_000 });
await fxa_sign_in.signIn();
await expect(dashboard_page.userMenuAvatar).toBeVisible({ timeout: 30_000 });
});

// verify basic sign-out flow works
test.describe('basic sign-out flow', {
tag: '@prod-sanity'
}, () => {
test('able to log out of appointment', async ({ page }) => {
// todo: logout, verify back on splashscreen page
expect(true).toBeTruthy();
});
});

0 comments on commit cbb67d5

Please sign in to comment.