From a60738510875f770f9dbb0b3449dbcf2d473ada3 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:21:16 +0100 Subject: [PATCH] test(e2e): resolve regressions and flakes --- .../profiles/oidc/login/error.spec.ts | 8 +- .../profiles/oidc/registration/error.spec.ts | 6 +- .../oidc/registration/success.spec.ts | 32 ++-- .../profiles/oidc/settings/success.spec.ts | 2 +- test/e2e/cypress/support/commands.ts | 164 ++++++++++-------- test/e2e/cypress/support/index.d.ts | 3 +- 6 files changed, 113 insertions(+), 102 deletions(-) diff --git a/test/e2e/cypress/integration/profiles/oidc/login/error.spec.ts b/test/e2e/cypress/integration/profiles/oidc/login/error.spec.ts index 4fbf97cb1db2..47bb7dae43be 100644 --- a/test/e2e/cypress/integration/profiles/oidc/login/error.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/login/error.spec.ts @@ -27,7 +27,7 @@ context('Social Sign In Errors', () => { }) it('should fail when the login request is rejected', () => { - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#reject').click() cy.location('pathname').should('equal', '/login') cy.get(appPrefix(app) + '[data-testid="ui/message/4000001"]').should( @@ -39,7 +39,7 @@ context('Social Sign In Errors', () => { it('should fail when the consent request is rejected', () => { const email = gen.email() - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').type(email) cy.get('#accept').click() cy.get('#reject').click() @@ -53,7 +53,7 @@ context('Social Sign In Errors', () => { it('should fail when the id_token is missing', () => { const email = gen.email() - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').type(email) cy.get('#accept').click() cy.get('#website').type(website) @@ -70,7 +70,7 @@ context('Social Sign In Errors', () => { const email = gen.email() cy.visit(login) - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').clear().type(email) cy.get('#remember').click() diff --git a/test/e2e/cypress/integration/profiles/oidc/registration/error.spec.ts b/test/e2e/cypress/integration/profiles/oidc/registration/error.spec.ts index 1d4f50ba6d36..b323ef1dfb5e 100644 --- a/test/e2e/cypress/integration/profiles/oidc/registration/error.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/registration/error.spec.ts @@ -27,7 +27,7 @@ context('Social Sign Up Errors', () => { }) it('should fail when the login request is rejected', () => { - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#reject').click() cy.location('pathname').should('equal', '/registration') cy.get(appPrefix(app) + '[data-testid="ui/message/4000001"]').should( @@ -39,7 +39,7 @@ context('Social Sign Up Errors', () => { it('should fail when the consent request is rejected', () => { const email = gen.email() - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').type(email) cy.get('#accept').click() cy.get('#reject').click() @@ -53,7 +53,7 @@ context('Social Sign Up Errors', () => { it('should fail when the id_token is missing', () => { const email = gen.email() - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').type(email) cy.get('#accept').click() cy.get('#website').type(website) diff --git a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts index eb50a38e63d8..0a5872539849 100644 --- a/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/registration/success.spec.ts @@ -1,6 +1,6 @@ -import { APP_URL, appPrefix, gen, website } from '../../../../helpers' -import { routes as react } from '../../../../helpers/react' -import { routes as express } from '../../../../helpers/express' +import {appPrefix, gen, website} from '../../../../helpers' +import {routes as react} from '../../../../helpers/react' +import {routes as express} from '../../../../helpers/express' context('Social Sign Up Successes', () => { ;[ @@ -16,7 +16,7 @@ context('Social Sign Up Successes', () => { app: 'express' as 'express', profile: 'oidc' } - ].forEach(({ registration, login, profile, app }) => { + ].forEach(({registration, login, profile, app}) => { describe(`for app ${app}`, () => { before(() => { cy.useConfigProfile(profile) @@ -32,10 +32,8 @@ context('Social Sign Up Successes', () => { }) const shouldSession = (email) => (session) => { - const { identity } = session + const {identity} = session expect(identity.id).to.not.be.empty - expect(identity.schema_id).to.equal('default') - expect(identity.schema_url).to.equal(`${APP_URL}/schemas/default`) expect(identity.traits.website).to.equal(website) expect(identity.traits.email).to.equal(email) } @@ -43,7 +41,7 @@ context('Social Sign Up Successes', () => { it('should be able to sign up with incomplete data and finally be signed in', () => { const email = gen.email() - cy.registerOidc({ email, expectSession: false, route: registration }) + cy.registerOidc({email, expectSession: false, route: registration}) cy.get('#registration-password').should('not.exist') cy.get(appPrefix(app) + '[name="traits.email"]').should( @@ -84,9 +82,11 @@ context('Social Sign Up Successes', () => { cy.get('[name="traits.consent"]').should('be.checked') cy.get('[name="traits.newsletter"]').should('be.checked') - cy.triggerOidc() + cy.triggerOidc(app) - cy.location('pathname').should('not.contain', '/consent') + cy.location('pathname').should((loc) => { + expect(loc).to.be.oneOf(['/welcome', '/']) + }) cy.getSession().should((session) => { shouldSession(email)(session) @@ -97,18 +97,18 @@ context('Social Sign Up Successes', () => { it('should be able to sign up with complete data', () => { const email = gen.email() - cy.registerOidc({ email, website, route: registration }) + cy.registerOidc({email, website, route: registration}) cy.getSession().should(shouldSession(email)) }) it('should be able to convert a sign up flow to a sign in flow', () => { const email = gen.email() - cy.registerOidc({ email, website, route: registration }) + cy.registerOidc({email, website, route: registration}) cy.logout() cy.noSession() cy.visit(registration) - cy.triggerOidc() + cy.triggerOidc(app) cy.location('pathname').should((path) => { expect(path).to.oneOf(['/', '/welcome']) @@ -124,7 +124,7 @@ context('Social Sign Up Successes', () => { const email = gen.email() cy.visit(login) - cy.triggerOidc() + cy.triggerOidc(app) cy.get('#username').clear().type(email) cy.get('#remember').click() @@ -139,7 +139,7 @@ context('Social Sign Up Successes', () => { ) cy.get('[name="traits.website"]').type('http://s') - cy.triggerOidc() + cy.triggerOidc(app) cy.get('[data-testid="ui/message/4000001"]').should( 'contain.text', @@ -152,7 +152,7 @@ context('Social Sign Up Successes', () => { .should('have.value', 'http://s') .clear() .type(website) - cy.triggerOidc() + cy.triggerOidc(app) cy.location('pathname').should('not.contain', '/registration') diff --git a/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts b/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts index fb7adb83374d..6635ae21e54c 100644 --- a/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts +++ b/test/e2e/cypress/integration/profiles/oidc/settings/success.spec.ts @@ -37,7 +37,7 @@ context('Social Sign In Settings Success', () => { cy.get('#accept').click() cy.get('input[name="traits.website"]').clear().type(website) - cy.triggerOidc('hydra') + cy.triggerOidc(app,'hydra') cy.get('[data-testid="ui/message/4000007"]').should( 'contain.text', diff --git a/test/e2e/cypress/support/commands.ts b/test/e2e/cypress/support/commands.ts index f9e970cc1bc4..1c2473bb63f8 100644 --- a/test/e2e/cypress/support/commands.ts +++ b/test/e2e/cypress/support/commands.ts @@ -13,19 +13,19 @@ import { import dayjs from 'dayjs' import YAML from 'yamljs' -import { Session } from '@ory/kratos-client' +import {Session} from '@ory/kratos-client' const configFile = 'kratos.generated.yml' const mergeFields = (form, fields) => { const result = {} - form.nodes.forEach(({ attributes, type }) => { + form.nodes.forEach(({attributes, type}) => { if (type === 'input') { result[attributes.name] = attributes.value } }) - return { ...result, ...fields } + return {...result, ...fields} } const updateConfigFile = (cb: (arg: any) => any) => { @@ -50,8 +50,7 @@ Cypress.Commands.add('proxy', (app: string) => { cy.wait(200) cy.visit(APP_URL + '/') cy.get(`[data-testid="app-${app}"]`).should('exist') - cy.clearAllCookies() - cy.visit(APP_URL + '/') + cy.wait(500) }) Cypress.Commands.add('shortPrivilegedSessionTime', ({} = {}) => { @@ -136,7 +135,7 @@ Cypress.Commands.add('longRecoveryLifespan', ({} = {}) => { Cypress.Commands.add('enableLoginForVerifiedAddressOnly', () => { updateConfigFile((config) => { config.selfservice.flows.login['after'] = { - password: { hooks: [{ hook: 'require_verified_address' }] } + password: {hooks: [{hook: 'require_verified_address'}]} } return config }) @@ -231,12 +230,12 @@ Cypress.Commands.add('useLaxAal', ({} = {}) => { Cypress.Commands.add( 'register', ({ - email = gen.email(), - password = gen.password(), - query = {}, - fields = {} - } = {}) => { - console.log('Creating user account: ', { email, password }) + email = gen.email(), + password = gen.password(), + query = {}, + fields = {} + } = {}) => { + console.log('Creating user account: ', {email, password}) // see https://github.com/cypress-io/cypress/issues/408 cy.visit(APP_URL) @@ -250,7 +249,7 @@ Cypress.Commands.add( }, qs: query }) - .then(({ body, status }) => { + .then(({body, status}) => { expect(status).to.eq(200) const form = body.ui return cy.request({ @@ -265,7 +264,7 @@ Cypress.Commands.add( followRedirect: false }) }) - .then(({ body }) => { + .then(({body}) => { expect(body.identity.traits.email).to.contain(email) }) } @@ -273,12 +272,12 @@ Cypress.Commands.add( Cypress.Commands.add( 'registerApi', - ({ email = gen.email(), password = gen.password(), fields = {} } = {}) => + ({email = gen.email(), password = gen.password(), fields = {}} = {}) => cy .request({ url: APP_URL + '/self-service/registration/api' }) - .then(({ body }) => { + .then(({body}) => { const form = body.ui return cy.request({ method: form.method, @@ -291,17 +290,17 @@ Cypress.Commands.add( url: form.action }) }) - .then(({ body }) => { + .then(({body}) => { expect(body.identity.traits.email).to.contain(email) }) ) -Cypress.Commands.add('settingsApi', ({ fields = {} } = {}) => +Cypress.Commands.add('settingsApi', ({fields = {}} = {}) => cy .request({ url: APP_URL + '/self-service/settings/api' }) - .then(({ body }) => { + .then(({body}) => { const form = body.ui return cy.request({ method: form.method, @@ -311,17 +310,17 @@ Cypress.Commands.add('settingsApi', ({ fields = {} } = {}) => url: form.action }) }) - .then(({ body }) => { + .then(({body}) => { expect(body.statusCode).to.eq(200) }) ) -Cypress.Commands.add('loginApi', ({ email, password } = {}) => +Cypress.Commands.add('loginApi', ({email, password} = {}) => cy .request({ url: APP_URL + '/self-service/login/api' }) - .then(({ body }) => { + .then(({body}) => { const form = body.ui return cy.request({ method: form.method, @@ -333,12 +332,12 @@ Cypress.Commands.add('loginApi', ({ email, password } = {}) => url: form.action }) }) - .then(({ body }) => { + .then(({body}) => { expect(body.session.identity.traits.email).to.contain(email) }) ) -Cypress.Commands.add('loginApiWithoutCookies', ({ email, password } = {}) => { +Cypress.Commands.add('loginApiWithoutCookies', ({email, password} = {}) => { cy.task('httpRequest', { url: APP_URL + '/self-service/login/api', headers: { @@ -365,21 +364,21 @@ Cypress.Commands.add('loginApiWithoutCookies', ({ email, password } = {}) => { }) }) -Cypress.Commands.add('recoverApi', ({ email, returnTo }) => { +Cypress.Commands.add('recoverApi', ({email, returnTo}) => { let url = APP_URL + '/self-service/recovery/api' if (returnTo) { url += '?return_to=' + returnTo } - cy.request({ url }) - .then(({ body }) => { + cy.request({url}) + .then(({body}) => { const form = body.ui return cy.request({ method: form.method, - body: mergeFields(form, { email, method: 'link' }), + body: mergeFields(form, {email, method: 'link'}), url: form.action }) }) - .then(({ body }) => { + .then(({body}) => { expect(body.state).to.contain('sent_email') }) }) @@ -387,16 +386,16 @@ Cypress.Commands.add('recoverApi', ({ email, returnTo }) => { Cypress.Commands.add( 'registerOidc', ({ - email, - website, - scopes, - rememberLogin = true, - rememberConsent = true, - acceptLogin = true, - acceptConsent = true, - expectSession = true, - route = APP_URL + '/registration' - }) => { + email, + website, + scopes, + rememberLogin = true, + rememberConsent = true, + acceptLogin = true, + acceptConsent = true, + expectSession = true, + route = APP_URL + '/registration' + }) => { cy.visit(route) cy.triggerOidc() @@ -465,7 +464,7 @@ Cypress.Commands.add('browserReturnUrlOry', ({} = {}) => { Cypress.Commands.add( 'loginOidc', - ({ expectSession = true, url = APP_URL + '/login' }) => { + ({expectSession = true, url = APP_URL + '/login'}) => { cy.visit(url) cy.triggerOidc('hydra') cy.location('href').should('not.eq', '/consent') @@ -479,11 +478,11 @@ Cypress.Commands.add( Cypress.Commands.add( 'login', - ({ email, password, expectSession = true, cookieUrl = APP_URL }) => { + ({email, password, expectSession = true, cookieUrl = APP_URL}) => { if (expectSession) { - console.log('Singing in user: ', { email, password }) + console.log('Singing in user: ', {email, password}) } else { - console.log('Attempting user sign in: ', { email, password }) + console.log('Attempting user sign in: ', {email, password}) } // see https://github.com/cypress-io/cypress/issues/408 @@ -499,7 +498,7 @@ Cypress.Commands.add( Accept: 'application/json' } }) - .then(({ body, status }) => { + .then(({body, status}) => { expect(status).to.eq(200) const form = body.ui return cy.request({ @@ -517,7 +516,7 @@ Cypress.Commands.add( failOnStatusCode: false }) }) - .then(({ status }) => { + .then(({status}) => { console.log('Login sequence completed: ', { email, password, @@ -534,7 +533,7 @@ Cypress.Commands.add( } ) -Cypress.Commands.add('loginMobile', ({ email, password }) => { +Cypress.Commands.add('loginMobile', ({email, password}) => { cy.visit(MOBILE_URL + '/Login') cy.get('input[data-testid="password_identifier"]').type(email) cy.get('input[data-testid="password"]').type(password) @@ -544,7 +543,7 @@ Cypress.Commands.add('loginMobile', ({ email, password }) => { Cypress.Commands.add('logout', () => { cy.getCookies().should((cookies) => { const c = cookies.find( - ({ name }) => name.indexOf('ory_kratos_session') > -1 + ({name}) => name.indexOf('ory_kratos_session') > -1 ) expect(c).to.not.be.undefined cy.clearCookie(c.name) @@ -555,12 +554,12 @@ Cypress.Commands.add('logout', () => { Cypress.Commands.add( 'reauth', ({ - expect: { email, success = true }, - type: { email: temail, password: tpassword } = { - email: undefined, - password: undefined - } - }) => { + expect: {email, success = true}, + type: {email: temail, password: tpassword} = { + email: undefined, + password: undefined + } + }) => { cy.location('pathname').should('contain', '/login') cy.get('input[name="password_identifier"]').should('have.value', email) if (temail) { @@ -577,13 +576,13 @@ Cypress.Commands.add( } ) -Cypress.Commands.add('deleteMail', ({ atLeast = 0 } = {}) => { +Cypress.Commands.add('deleteMail', ({atLeast = 0} = {}) => { let tries = 0 let count = 0 const req = () => cy - .request('DELETE', `${MAIL_API}/mail`, { pruneCode: 'all' }) - .then(({ body }) => { + .request('DELETE', `${MAIL_API}/mail`, {pruneCode: 'all'}) + .then(({body}) => { count += parseInt(body) if (count < atLeast && tries < 100) { cy.log( @@ -602,7 +601,7 @@ Cypress.Commands.add('deleteMail', ({ atLeast = 0 } = {}) => { Cypress.Commands.add( 'getSession', - ({ expectAal = 'aal1', expectMethods = [] } = {}) => + ({expectAal = 'aal1', expectMethods = []} = {}) => cy.request('GET', `${KRATOS_PUBLIC}/sessions/whoami`).then((response) => { expect(response.body.id).to.not.be.empty expect(dayjs().isBefore(dayjs(response.body.expires_at))).to.be.true @@ -624,7 +623,7 @@ Cypress.Commands.add( expectMethods.forEach((value) => { expect( response.body.authentication_methods.find( - ({ method }) => method === value + ({method}) => method === value ) ).to.exist }) @@ -646,7 +645,7 @@ Cypress.Commands.add('noSession', () => return request }) ) -Cypress.Commands.add('getIdentityByEmail', ({ email }) => +Cypress.Commands.add('getIdentityByEmail', ({email}) => cy .request({ method: 'GET', @@ -662,8 +661,8 @@ Cypress.Commands.add('getIdentityByEmail', ({ email }) => Cypress.Commands.add( 'performEmailVerification', ({ - expect: { email, redirectTo } = { email: undefined, redirectTo: undefined } - } = {}) => + expect: {email, redirectTo} = {email: undefined, redirectTo: undefined} + } = {}) => cy.getMail().then((message) => { expect(message.subject.trim()).to.equal( 'Please verify your email address' @@ -676,7 +675,7 @@ Cypress.Commands.add( expect(link).to.not.be.null expect(link.href).to.contain(APP_URL) - cy.request({ url: link.href, followRedirect: false }).should( + cy.request({url: link.href, followRedirect: false}).should( (response) => { expect(response.status).to.eq(303) if (redirectTo) { @@ -691,16 +690,16 @@ Cypress.Commands.add( Cypress.Commands.add( 'verifyEmail', - ({ expect: { email, password, redirectTo } }) => - cy.performEmailVerification({ expect: { email, redirectTo } }).then(() => { + ({expect: {email, password, redirectTo}}) => + cy.performEmailVerification({expect: {email, redirectTo}}).then(() => { cy.getSession().should((session) => - assertVerifiableAddress({ email, isVerified: true })(session) + assertVerifiableAddress({email, isVerified: true})(session) ) }) ) // Uses the verification email but waits so that it expires -Cypress.Commands.add('recoverEmailButExpired', ({ expect: { email } }) => { +Cypress.Commands.add('recoverEmailButExpired', ({expect: {email}}) => { cy.getMail().should((message) => { expect(message.subject.trim()).to.equal('Recover access to your account') expect(message.toAddresses[0].trim()).to.equal(email) @@ -715,7 +714,7 @@ Cypress.Commands.add('recoverEmailButExpired', ({ expect: { email } }) => { Cypress.Commands.add( 'recoverEmail', - ({ expect: { email }, shouldVisit = true }) => + ({expect: {email}, shouldVisit = true}) => cy.getMail().should((message) => { expect(message.subject.trim()).to.equal('Recover access to your account') expect(message.fromAddress.trim()).to.equal('no-reply@ory.kratos.sh') @@ -736,7 +735,7 @@ Cypress.Commands.add( // Uses the verification email but waits so that it expires Cypress.Commands.add( 'verifyEmailButExpired', - ({ expect: { email, password } }) => + ({expect: {email, password}}) => cy.getMail().then((message) => { expect(message.subject.trim()).to.equal( 'Please verify your email address' @@ -776,7 +775,7 @@ Cypress.Commands.add('waitForPrivilegedSessionToExpire', () => { expect(session.authenticated_at).to.not.be.empty cy.wait( dayjs(session.authenticated_at).add(privilegedLifespan).diff(dayjs()) + - 100 + 100 ) }) }) @@ -792,7 +791,7 @@ Cypress.Commands.add('expectSettingsSaved', () => .should('contain.text', 'Your changes have been saved') ) -Cypress.Commands.add('getMail', ({ removeMail = true } = {}) => { +Cypress.Commands.add('getMail', ({removeMail = true} = {}) => { let tries = 0 const req = () => cy.request(`${MAIL_API}/mail`).then((response) => { @@ -807,7 +806,7 @@ Cypress.Commands.add('getMail', ({ removeMail = true } = {}) => { expect(count).to.equal(1) if (removeMail) { return cy - .deleteMail({ atLeast: count }) + .deleteMail({atLeast: count}) .then(() => Promise.resolve(response.body.mailItems[0])) } @@ -818,7 +817,7 @@ Cypress.Commands.add('getMail', ({ removeMail = true } = {}) => { }) Cypress.Commands.add('clearAllCookies', () => { - cy.clearCookies({ domain: null }) + cy.clearCookies({domain: null}) }) Cypress.Commands.add('submitPasswordForm', () => { @@ -848,8 +847,8 @@ Cypress.Commands.add('shouldShow2FAScreen', () => { Cypress.Commands.add( 'shouldErrorOnDisallowedReturnTo', - (init: string, { app }: { app: 'express' | 'react' }) => { - cy.visit(init, { failOnStatusCode: false }) + (init: string, {app}: { app: 'express' | 'react' }) => { + cy.visit(init, {failOnStatusCode: false}) if (app === 'react') { cy.location('href').should('include', init.split('?')[0]) cy.get('.Toastify').should( @@ -868,7 +867,7 @@ Cypress.Commands.add( Cypress.Commands.add( 'shouldHaveCsrfError', - ({ app }: { app: 'express' | 'react' }) => { + ({app}: { app: 'express' | 'react' }) => { let initial let pathname cy.location().should((location) => { @@ -877,7 +876,7 @@ Cypress.Commands.add( }) cy.getCookies().should((cookies) => { - const csrf = cookies.find(({ name }) => name.indexOf('csrf') > -1) + const csrf = cookies.find(({name}) => name.indexOf('csrf') > -1) expect(csrf).to.not.be.undefined cy.clearCookie(csrf.name) }) @@ -904,6 +903,17 @@ Cypress.Commands.add( } ) -Cypress.Commands.add('triggerOidc', (provider: string = 'hydra') => { +Cypress.Commands.add('triggerOidc', (app: 'react' | 'express', provider: string = 'hydra') => { + let initial, didHaveSearch + cy.location().then(loc => { + didHaveSearch = loc.search.length > 0 + initial = loc.pathname + loc.search + }) cy.get('[name="provider"][value="' + provider + '"]').click() + cy.location().then(loc => { + if (app === 'express' || didHaveSearch) { + return + } + expect(loc.pathname + loc.search).not.to.eql(initial) + }) }) diff --git a/test/e2e/cypress/support/index.d.ts b/test/e2e/cypress/support/index.d.ts index 3717159436c3..8815665f394e 100644 --- a/test/e2e/cypress/support/index.d.ts +++ b/test/e2e/cypress/support/index.d.ts @@ -278,9 +278,10 @@ declare global { /** * Triggers a Social Sign In flow for the given provider * + * @param app * @param provider */ - triggerOidc(provider?: string): Chainable + triggerOidc(app: 'react' | 'express', provider?: string): Chainable /** * Changes the config so that the recovery privileged lifespan is very long.