Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

Commit

Permalink
Improve e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BryceBarbara committed Oct 5, 2022
1 parent f722671 commit d908ca3
Show file tree
Hide file tree
Showing 18 changed files with 249 additions and 73 deletions.
1 change: 0 additions & 1 deletion docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@ TODO: Add a description for this document and what readers can expect to learn f
<route lang="yaml">
meta:
requiresAuth: false # indicates whether the route requires authentication
requiresGuest: true # indicates whether the route is only available to guests
</route>
```
44 changes: 22 additions & 22 deletions eng/scripts/populate-firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,7 @@ const firebaseAppDetailDefaults = {

class Script {
/**
*
* @param {string[]} args
* @returns {ScriptOptions}
*/
static getScriptOptionsFromArgs(args) {
/** @type {ScriptOptions} */
const options = {
detailsFileName: defaultDetailsFileName,
forceEnabled: false,
}

options.forceEnabled = args.includes('--force')
if (options.forceEnabled)
args = args.filter(arg => arg !== '--force')
if (args.length > 0 && args[0])
options.detailsFileName = args[0]

return options
}

/**
*
* The main entry point for the script.
* @param {string[]} args
*/
static async main(args) {
Expand All @@ -77,6 +56,27 @@ class Script {
const authProviderFilePath = path.join(process.cwd(), 'frontend/src/firebase/auth-provider.g.ts')
FirebaseTools.updateAuthProviderTs(authProviderFilePath, fbDetails, options)
}

/**
* Attempts to parse the script options from the given command line arguments.
* @param {string[]} args The command line arguments.
* @returns {ScriptOptions}
*/
static getScriptOptionsFromArgs(args) {
/** @type {ScriptOptions} */
const options = {
detailsFileName: defaultDetailsFileName,
forceEnabled: false,
}

options.forceEnabled = args.includes('--force')
if (options.forceEnabled)
args = args.filter(arg => arg !== '--force')
if (args.length > 0 && args[0])
options.detailsFileName = args[0]

return options
}
}

class FirebaseTools {
Expand Down
3 changes: 3 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Avoid commiting sensitive data
cypress.env.json
serviceAccount.json
6 changes: 5 additions & 1 deletion frontend/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { defineConfig } from 'cypress'
import admin from 'firebase-admin'
import { plugin as cypressFirebasePlugin } from 'cypress-firebase'

export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3333',
chromeWebSecurity: false,
specPattern: 'cypress/e2e/**/*.spec.*',
supportFile: false,
setupNodeEvents(on, config) {
cypressFirebasePlugin(on, config, admin)
},
},
})
3 changes: 3 additions & 0 deletions frontend/cypress.env.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"TEST_UID": "0000000000000000000000000000"
}
69 changes: 69 additions & 0 deletions frontend/cypress/e2e/api-test-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const Locators = {
welcomeMessageRefreshButton: '[data-test=welcome-message-refresh-button]',
welcomeMessageResult: '[data-test=welcome-message-result]',
remoteMathInput: '[data-test=remote-math-input]',
remoteMathSubmitButton: '[data-test=remote-math-submit-button]',
remoteMathResultOutput: '[data-test=remote-math-result-output]',
remoteMathResultWhen: '[data-test=remote-math-result-when]',
}

function normalizeNumberString(str: string) {
return str.replace(/[^0-9.]/g, '')
}

context('API Test Page', () => {
before(() => {
cy.login()
.visit('/api-test')
})

context('Welcome Message', () => {
it('Can refresh welcome message', () => {
cy.intercept('POST', '/*/*/getWelcomeMessage')
.as('getWelcomeMessage')

// The refresh button for the welcome message should be visible
cy.get(Locators.welcomeMessageRefreshButton)
.should('be.visible')

// Clicking the refresh button should trigger a request to the API
cy.get(Locators.welcomeMessageRefreshButton)
.click()
.wait('@getWelcomeMessage')

// The result should be visible
cy.get(Locators.welcomeMessageResult)
.should('be.visible')
})
})

context('Remote Math', () => {
it('Can get accurate results', () => {
const inputs = [123, 456]

cy.intercept('POST', '/*/*/calculateSquare')
.as('calculateSquare')

for (const input of inputs) {
cy.get(Locators.remoteMathInput)
.clear()
.type(input.toString())

// Clicking the submit button should trigger a request to the API
cy.get(Locators.remoteMathSubmitButton)
.click()
.wait('@calculateSquare')

// The result should be visible
cy.get(Locators.remoteMathResultWhen)
.should('be.visible')
cy.get(Locators.remoteMathResultOutput)
.should('be.visible')
.should((output) => {
// The result should be accurate
expect(normalizeNumberString(output.text())).to.eq((input * input).toString())
})
}
})
})
})
36 changes: 0 additions & 36 deletions frontend/cypress/e2e/basic.spec.ts

This file was deleted.

26 changes: 26 additions & 0 deletions frontend/cypress/e2e/login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
context('Login', () => {
it('Detects logging in', () => {
cy.logout()
.visit('/')
.url()
.should('eq', 'http://localhost:3333/')

cy.get('[data-test=login-button]')
.should('exist')

// Login which app should detect
cy.login()

cy.get('[data-test=logout-button]')
.should('exist')
})

it('Can log out', () => {
cy.get('[data-test=logout-button]')
.should('exist')
.click()

cy.get('[data-test=login-button]')
.should('exist')
})
})
12 changes: 12 additions & 0 deletions frontend/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import firebase from 'firebase/compat/app'
import 'firebase/compat/auth'
import 'firebase/compat/database'
import 'firebase/compat/firestore'
import { attachCustomCommands } from 'cypress-firebase'
import { app } from '../../src/firebase/app.g'

firebase.initializeApp({
...app.options,
})

attachCustomCommands({ Cypress, cy, firebase })
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
"critters": "^0.0.16",
"cross-env": "^7.0.3",
"cypress": "^10.9.0",
"cypress-firebase": "^2.2.2",
"eslint": "^8.24.0",
"eslint-plugin-cypress": "^2.12.1",
"firebase-admin": "^10.3.0",
"https-localhost": "^4.7.1",
"jsdom": "^20.0.1",
"markdown-it-link-attributes": "^4.0.1",
Expand Down
12 changes: 12 additions & 0 deletions frontend/serviceAccount.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "service_account",
"project_id": "",
"private_key_id": "",
"private_key": "",
"client_email": "",
"client_id": "",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": ""
}
8 changes: 7 additions & 1 deletion frontend/src/components/layout/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const showLanguageSelector = computed(() => availableLocales.length > 1)
:title="t('button.toggle-sidebar')"
class="sidebar-toggle"
:class="{ flipped: sidebarCollapsed }"
data-test="sidebar-toggle-button"
@click="() => toggleSidebar()"
>
<i-carbon-chevron-left class="icon" />
Expand All @@ -32,7 +33,12 @@ const showLanguageSelector = computed(() => availableLocales.length > 1)
<component :is="themeIcon" />
</template>
</NButton>
<NButton quaternary :title="t('button.sign-out-title')" @click="signOut">
<NButton
quaternary
:title="t('button.sign-out-title')"
data-test="logout-button"
@click="signOut"
>
{{ t('button.sign-out') }}
</NButton>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/layout/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,6 @@ function findMatchingMenuToRoute(routePath: string): MenuOption | null {
:collapsed-width="sidebarCollapsedWidth"
:options="menuOptions"
:render-label="renderMenuLabel"
data-test="sidebar"
/>
</template>
22 changes: 17 additions & 5 deletions frontend/src/components/pages/ApiTestPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ const remoteMathErrorMessage = computed(() => {
<NAlert v-else-if="welcomeErrorMessage" type="error">
{{ welcomeErrorMessage }}
</NAlert>
<p v-else-if="welcomeMessage.state.value" leading-none>
<p v-else-if="welcomeMessage.state.value" leading-none data-test="welcome-message-result">
<span mr-1>
{{ t('pages.api-test.welcome-test.label.server-result') }}
</span>
<span>{{ welcomeMessage.state.value.message }}</span>
</p>
<template #action>
<NButton :loading="welcomeMessage.isLoading.value" @click="() => welcomeMessage.execute()">
<NButton :loading="welcomeMessage.isLoading.value" data-test="welcome-message-refresh-button" @click="() => welcomeMessage.execute()">
{{ t('pages.api-test.welcome-test.button.refresh') }}
</NButton>
</template>
Expand All @@ -72,10 +72,11 @@ const remoteMathErrorMessage = computed(() => {
:placeholder="t('pages.api-test.remote-math-test.placeholder.number-input')"
clearable
min-w-280px
data-test="remote-math-input"
/>
</NFormItem>
<NFormItem>
<NButton :loading="mathTest.isLoading.value" attr-type="submit">
<NButton :loading="mathTest.isLoading.value" attr-type="submit" data-test="remote-math-submit-button">
{{ t('pages.api-test.remote-math-test.button.submit') }}
</NButton>
</NFormItem>
Expand All @@ -84,8 +85,19 @@ const remoteMathErrorMessage = computed(() => {
<span mr-1>
{{ t('pages.api-test.remote-math-test.label.server-result') }}
</span>
<n-skeleton v-if="mathTest.isLoading.value" text style="width: 5ch" />
<span v-else-if="mathTest.state.value">{{ t('pages.api-test.remote-math-test.result', { output: n(mathTest.state.value.output), when: d(mathTest.state.value.date, 'long') }) }}</span>
<n-skeleton v-if="mathTest.isLoading.value" text style="width: 20ch" />
<i18n-t
v-else-if="mathTest.state.value"
keypath="pages.api-test.remote-math-test.result"
tag="span"
>
<template #output>
<span data-test="remote-math-result-output">{{ n(mathTest.state.value.output) }}</span>
</template>
<template #when>
<span data-test="remote-math-result-when">{{ d(mathTest.state.value.date, 'long') }}</span>
</template>
</i18n-t>
<span v-else>{{ t('pages.api-test.remote-math-test.error.missing-result') }}</span>
</p>
<NAlert v-else-if="remoteMathErrorMessage" type="error">
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/modules/auth-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@ import { type UserModule } from '~/types'
export const install: UserModule = ({ router }) => {
router.beforeEach(async (to, from, next) => {
const requiresAuth = to.matched.some(x => x.meta.requiresAuth)
const requiresGuest = to.matched.some(x => x.meta.requiresGuest)

if (requiresAuth && !auth.currentUser) {
// console.log('Redirecting to login...')
next({ name: 'login' })
}
else if (requiresGuest && auth.currentUser) {
// console.log('Redirecting to home...')
next('/')
}
else {
next()
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ onMounted(() => {
:disabled="isSigningIn"
type="primary"
important-w-full
data-test="login-button"
@click="signIn"
>
{{ t('button.sign-in') }}
Expand All @@ -98,6 +99,5 @@ onMounted(() => {
<route lang="yaml">
meta:
requiresAuth: false
requiresGuest: true
layout: bare
</route>
2 changes: 1 addition & 1 deletion frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default defineConfig({
extensions: ['vue', 'md'],
extendRoute(route) {
// Any route that isn't explicitly set not to public will be made to require auth
const isAnonymous = route.meta && (route.meta.requiresAuth === false || route.meta.requiresGuest === true)
const isAnonymous = route.meta && (route.meta.requiresAuth === false)
if (isAnonymous)
return route
// Augment the route with meta that indicates that the route requires authentication.
Expand Down
Loading

0 comments on commit d908ca3

Please sign in to comment.