test: login more directly (#44467)

* test: login more directly

* test: separate login from other visits

In a single test, Cypress can only visit within a single domain, hence
the separation.

* chore: fail slowly for all strategies

* test: user certified user for showing cert

* test: fix and cleanup certifications
This commit is contained in:
Oliver Eyton-Williams
2021-12-11 10:04:16 +01:00
committed by GitHub
parent 8fb945c5a8
commit 48f88428e8
11 changed files with 100 additions and 157 deletions

View File

@ -62,6 +62,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: build-client needs: build-client
strategy: strategy:
fail-fast: false
matrix: matrix:
browsers: [chrome, firefox, electron] browsers: [chrome, firefox, electron]
node-version: [16.x] node-version: [16.x]

View File

@ -15,6 +15,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
@ -47,6 +48,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]

View File

@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
@ -39,6 +40,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
@ -70,6 +72,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
@ -103,6 +106,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
locale: [chinese, espanol] locale: [chinese, espanol]

View File

@ -1,112 +1,44 @@
import { SuperBlocks } from '../../config/certification-settings'; const certifiedUser = '/certification/certifieduser/responsive-web-design';
const certificationUrl = '/certification/developmentuser/responsive-web-design';
const projects = {
superBlock: SuperBlocks.RespWebDesign,
block: 'responsive-web-design-projects',
challenges: [
{
slug: 'build-a-tribute-page',
solution: 'https://codepen.io/moT01/pen/ZpJpKp'
},
{
slug: 'build-a-survey-form',
solution: 'https://codepen.io/moT01/pen/LrrjGz?editors=1010'
},
{
slug: 'build-a-product-landing-page',
solution: 'https://codepen.io/moT01/full/qKyKYL/'
},
{
slug: 'build-a-technical-documentation-page',
solution: 'https://codepen.io/moT01/full/JBvzNL/'
},
{
slug: 'build-a-personal-portfolio-webpage',
solution: 'https://codepen.io/moT01/pen/vgOaoJ'
}
]
};
describe('A certification,', function () { describe('A certification,', function () {
before(() => { before(() => {
cy.exec('npm run seed'); cy.exec('npm run seed:certified-user');
cy.login();
// submit projects for certificate
const { superBlock, block, challenges } = projects;
challenges.forEach(({ slug, solution }) => {
const url = `/learn/${superBlock}/${block}/${slug}`;
cy.visit(url);
cy.get('#dynamic-front-end-form')
.get('#solution')
.type(solution, { force: true, delay: 0 });
cy.contains("I've completed this challenge")
.should('not.be.disabled')
.click();
cy.contains('Submit and go to next challenge').click().wait(1000);
});
cy.get('.donation-modal').should('be.visible');
cy.visit('/settings');
// set user settings to public to claim a cert
cy.get('label:contains(Public)>input').each(el => {
if (!/toggle-active/.test(el[0].parentElement.className)) {
cy.wrap(el).click({ force: true });
cy.wait(1000);
}
});
// if honest policy not accepted
cy.get('.honesty-policy button').then(btn => {
if (btn[0].innerText === 'Agree') {
btn[0].click({ force: true });
cy.wait(1000);
}
});
// claim certificate
cy.get('a[href*="developmentuser/responsive-web-design"]').click({
force: true
});
}); });
describe('while viewing your own,', function () { describe('while viewing your own,', function () {
before(() => { beforeEach(() => {
cy.login(); cy.login();
cy.visit(certificationUrl);
}); });
it('should render a LinkedIn button', function () { it('should render a LinkedIn button', function () {
cy.visit(certifiedUser);
cy.contains('Add this certification to my LinkedIn profile') cy.contains('Add this certification to my LinkedIn profile')
.should('have.attr', 'href') .should('have.attr', 'href')
.and( .and(
'match', 'match',
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/https:\/\/www\.linkedin\.com\/profile\/add\?startTask=CERTIFICATION_NAME&name=Responsive Web Design&organizationId=4831032&issueYear=\d\d\d\d&issueMonth=\d\d?&certUrl=https:\/\/freecodecamp\.org\/certification\/developmentuser\/responsive-web-design/ /https:\/\/www\.linkedin\.com\/profile\/add\?startTask=CERTIFICATION_NAME&name=Responsive Web Design&organizationId=4831032&issueYear=\d\d\d\d&issueMonth=\d\d?&certUrl=https:\/\/freecodecamp\.org\/certification\/certifieduser\/responsive-web-design/
); );
}); });
it('should render a Twitter button', function () { it('should render a Twitter button', function () {
cy.visit(certifiedUser);
cy.contains('Share this certification on Twitter').should( cy.contains('Share this certification on Twitter').should(
'have.attr', 'have.attr',
'href', 'href',
'https://twitter.com/intent/tweet?text=I just earned the Responsive Web Design certification @freeCodeCamp! Check it out here: https://freecodecamp.org/certification/developmentuser/responsive-web-design' 'https://twitter.com/intent/tweet?text=I just earned the Responsive Web Design certification @freeCodeCamp! Check it out here: https://freecodecamp.org/certification/certifieduser/responsive-web-design'
); );
}); });
it("should be issued with today's date", () => { it('should be issued with the submission date', () => {
const date = new Date(); cy.visit(certifiedUser);
const issued = `Issued\xa0${new Intl.DateTimeFormat('en-US', { const issued = `Issued\xa0August 3, 2018`;
month: 'long'
}).format(date)} ${date.getDate()}, ${date.getFullYear()}`;
cy.get('[data-cy=issue-date]').should('have.text', issued); cy.get('[data-cy=issue-date]').should('have.text', issued);
}); });
}); });
describe("while viewing someone else's,", function () { describe("while viewing someone else's,", function () {
before(() => { before(() => {
cy.visit(certificationUrl); cy.visit(certifiedUser);
}); });
it('should display certificate', function () { it('should display certificate', function () {

View File

@ -34,6 +34,7 @@ const pythonProjects = {
describe('project submission', () => { describe('project submission', () => {
beforeEach(() => { beforeEach(() => {
cy.exec('npm run seed'); cy.exec('npm run seed');
cy.login();
}); });
// NOTE: this will fail once challenge tests are added. // NOTE: this will fail once challenge tests are added.
it('Should be possible to submit Python projects', () => { it('Should be possible to submit Python projects', () => {
@ -46,7 +47,7 @@ describe('project submission', () => {
.type('https://replit.com/@camperbot/python-project#main.py'); .type('https://replit.com/@camperbot/python-project#main.py');
cy.contains("I've completed this challenge").click(); cy.contains("I've completed this challenge").click();
cy.contains('Go to next challenge'); cy.contains('go to next challenge');
// clicking on 'Go to next challenge' seems to have caused flakiness, so // clicking on 'Go to next challenge' seems to have caused flakiness, so
// it's commented out until we figure out why. // it's commented out until we figure out why.
// cy.contains('Go to next challenge').click(); // cy.contains('Go to next challenge').click();
@ -61,7 +62,6 @@ describe('project submission', () => {
'JavaScript projects can be submitted and then viewed in /settings and on the certifications', 'JavaScript projects can be submitted and then viewed in /settings and on the certifications',
{ browser: 'electron' }, { browser: 'electron' },
() => { () => {
cy.login();
cy.fixture('../../config/curriculum.json').then(curriculum => { cy.fixture('../../config/curriculum.json').then(curriculum => {
const { challenges, meta } = const { challenges, meta } =
curriculum[SuperBlocks.JsAlgoDataStruct].blocks[ curriculum[SuperBlocks.JsAlgoDataStruct].blocks[

View File

@ -32,7 +32,7 @@ function waitForAppStart() {
}); });
} }
describe('Navbar', () => { describe('Navbar when logged out', () => {
beforeEach(() => { beforeEach(() => {
appHasStarted = false; appHasStarted = false;
cy.visit('/', { cy.visit('/', {
@ -41,6 +41,32 @@ describe('Navbar', () => {
cy.viewport(1300, 660); cy.viewport(1300, 660);
}); });
it('Should have a "Sign in" button', () => {
cy.contains("[data-test-label='landing-small-cta']", 'Sign in');
});
it(
'Should have `Sign in` link on landing and learn pages' +
' when not signed in',
() => {
cy.contains(selectors.smallCallToAction, 'Sign in');
cy.get(selectors.menuButton).click();
cy.get(selectors.navigationLinks).contains('Curriculum').click();
cy.contains(selectors.smallCallToAction, 'Sign in');
}
);
});
describe('Navbar Logged in', () => {
beforeEach(() => {
cy.login();
appHasStarted = false;
cy.visit('/', {
onBeforeLoad: spyOnListener
}).then(waitForAppStart);
cy.viewport(1300, 660);
});
it('Should render properly', () => { it('Should render properly', () => {
cy.get('#universal-nav').should('be.visible'); cy.get('#universal-nav').should('be.visible');
cy.get('#universal-nav').should('have.class', 'universal-nav'); cy.get('#universal-nav').should('have.class', 'universal-nav');
@ -56,10 +82,6 @@ describe('Navbar', () => {
} }
); );
it('Should have a "Sign in" button', () => {
cy.contains("[data-test-label='landing-small-cta']", 'Sign in');
});
// have the curriculum and CTA on landing and /learn pages. // have the curriculum and CTA on landing and /learn pages.
it( it(
'Should have `Radio`, `Forum`, and `Curriculum` links on landing and learn pages' + 'Should have `Radio`, `Forum`, and `Curriculum` links on landing and learn pages' +
@ -75,32 +97,18 @@ describe('Navbar', () => {
} }
); );
it(
'Should have `Sign in` link on landing and learn pages' +
' when not signed in',
() => {
cy.contains(selectors.smallCallToAction, 'Sign in');
cy.get(selectors.menuButton).click();
cy.get(selectors.navigationLinks).contains('Curriculum').click();
cy.contains(selectors.smallCallToAction, 'Sign in');
}
);
it('Should have `Profile` link when user is signed in', () => { it('Should have `Profile` link when user is signed in', () => {
cy.login();
cy.get(selectors.menuButton).click(); cy.get(selectors.menuButton).click();
cy.get(selectors.navigationLinks).contains('Profile').click(); cy.get(selectors.navigationLinks).contains('Profile').click();
cy.url().should('include', '/developmentuser'); cy.url().should('include', '/developmentuser');
}); });
it('Should have a profile image with class `default-border`', () => { it('Should have a profile image with class `default-border`', () => {
cy.login();
cy.get(selectors.avatarContainer).should('have.class', 'default-border'); cy.get(selectors.avatarContainer).should('have.class', 'default-border');
cy.get(selectors.defaultAvatar).should('exist'); cy.get(selectors.defaultAvatar).should('exist');
}); });
it('Should have a profile image with dimensions that are <= 31px', () => { it('Should have a profile image with dimensions that are <= 31px', () => {
cy.login();
cy.get(selectors.avatarImage).invoke('width').should('lte', 31); cy.get(selectors.avatarImage).invoke('width').should('lte', 31);
cy.get(selectors.avatarImage).invoke('height').should('lte', 31); cy.get(selectors.avatarImage).invoke('height').should('lte', 31);
}); });

View File

@ -1,57 +1,40 @@
import '@testing-library/cypress/add-commands'; import '@testing-library/cypress/add-commands';
describe('Settings certifications area', () => { describe('Settings certifications area', () => {
describe('initially', () => {
before(() => { before(() => {
cy.exec('npm run seed'); cy.exec('npm run seed');
cy.login(); cy.login();
cy.visit('/settings');
}); });
describe('initially', () => { it('Should render the default settings page', () => {
it('Should render 15 "Claim Certification" buttons', () => { cy.visit('/settings/');
cy.findAllByText('Claim Certification').should($btns => { cy.findAllByText('Claim Certification').should($btns => {
expect($btns).to.have.length(15); expect($btns).to.have.length(15);
}); });
}); cy.findByText('Show Certification').should('not.exist');
cy.contains('Agree');
it('Should render zero "Show Certification" buttons', () => {
cy.contains('Show Certification').should('not.exist');
});
it('Should render one "Agree" button', () => {
cy.contains('Agree').should('exist');
});
describe('before isHonest', () => {
it('Should show "must agree" message when trying to claim a cert', () => {
cy.contains('Claim Certification').click(); cy.contains('Claim Certification').click();
cy.contains( cy.contains(
'To claim a certification, you must first accept our academic honesty policy' 'To claim a certification, you must first accept our academic honesty policy'
).should('exist'); );
}); });
}); });
describe('after isHonest', () => { describe('after isHonest', () => {
beforeEach(() => { before(() => {
cy.visit('/'); cy.exec('npm run seed');
cy.login(); cy.login();
});
it('Should update the user as they try to claim their certifications', () => {
cy.visit('/settings'); cy.visit('/settings');
}); cy.contains('Agree').click();
cy.contains('You have accepted our Academic Honesty Policy.');
it('Should render "You have accepted our Academic Honesty Policy." button after clicking "Agree"', () => { cy.contains('Claim Certification').click();
cy.contains('Agree').click({ force: true });
cy.contains('You have accepted our Academic Honesty Policy.').should(
'exist'
);
});
it('Should show "incompleted projects" message when clicking "Claim Certification"', () => {
cy.contains('Claim Certification').click({ force: true });
cy.contains( cy.contains(
'It looks like you have not completed the necessary steps. Please complete the required projects to claim the Responsive Web Design Certification' 'It looks like you have not completed the necessary steps. Please complete the required projects to claim the Responsive Web Design Certification'
).should('exist'); );
});
}); });
}); });
}); });

View File

@ -1,6 +1,8 @@
describe('Settings', () => { describe('Settings', () => {
it('should be possible to reset your progress', () => { before(() => {
cy.login(); cy.login();
});
it('should be possible to reset your progress', () => {
cy.visit('/settings'); cy.visit('/settings');
cy.contains('Reset all of my progress').click(); cy.contains('Reset all of my progress').click();
cy.contains('Reset everything. I want to start from the beginning').click(); cy.contains('Reset everything. I want to start from the beginning').click();

View File

@ -1,14 +1,18 @@
describe('Username input field', () => { describe('Username input field', () => {
beforeEach(() => { beforeEach(() => {
cy.login(); cy.login();
});
function goToSettings() {
cy.visit('/settings'); cy.visit('/settings');
// Setting aliases here // Setting aliases here
cy.get('input[name=username-settings]').as('usernameInput'); cy.get('input[name=username-settings]').as('usernameInput');
cy.get('form#usernameSettings').as('usernameForm'); cy.get('form#usernameSettings').as('usernameForm');
}); }
it('Should be possible to type', () => { it('Should be possible to type', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('twaha', { force: true }) .type('twaha', { force: true })
@ -16,6 +20,7 @@ describe('Username input field', () => {
}); });
it('Should show message when validating name', () => { it('Should show message when validating name', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('twaha', { force: true }); .type('twaha', { force: true });
@ -28,6 +33,7 @@ describe('Username input field', () => {
}); });
it('Should show username is available if it is', () => { it('Should show username is available if it is', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('brad', { force: true }); .type('brad', { force: true });
@ -41,6 +47,7 @@ describe('Username input field', () => {
}); });
it('Should info message if username is available', () => { it('Should info message if username is available', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('mrugesh', { force: true }); .type('mrugesh', { force: true });
@ -58,6 +65,7 @@ describe('Username input field', () => {
// eslint-disable-next-line // eslint-disable-next-line
it('Should be able to click the `Save` button if username is avalable', () => { it('Should be able to click the `Save` button if username is avalable', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('oliver', { force: true }); .type('oliver', { force: true });
@ -68,6 +76,7 @@ describe('Username input field', () => {
}); });
it('Should show username is unavailable if it is', () => { it('Should show username is unavailable if it is', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('twaha', { force: true }); .type('twaha', { force: true });
@ -82,6 +91,7 @@ describe('Username input field', () => {
// eslint-disable-next-line // eslint-disable-next-line
it('Should not be possible to click the `Save` button if username is unavailable', () => { it('Should not be possible to click the `Save` button if username is unavailable', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('twaha', { force: true }); .type('twaha', { force: true });
@ -97,6 +107,7 @@ describe('Username input field', () => {
}); });
it('Should not show anything if user types their current name', () => { it('Should not show anything if user types their current name', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('developmentuser', { force: true }); .type('developmentuser', { force: true });
@ -106,6 +117,7 @@ describe('Username input field', () => {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
it('Should not be possible to click the `Save` button if user types their current name', () => { it('Should not be possible to click the `Save` button if user types their current name', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('developmentuser', { force: true }); .type('developmentuser', { force: true });
@ -114,6 +126,7 @@ describe('Username input field', () => {
}); });
it('Should show warning if username includes invalid character', () => { it('Should show warning if username includes invalid character', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('Quincy Larson', { force: true }); .type('Quincy Larson', { force: true });
@ -128,6 +141,7 @@ describe('Username input field', () => {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
it('Should not be able to click the `Save` button if username includes invalid character', () => { it('Should not be able to click the `Save` button if username includes invalid character', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('Quincy Larson', { force: true }); .type('Quincy Larson', { force: true });
@ -136,6 +150,7 @@ describe('Username input field', () => {
}); });
it('Should change username if `Save` button is clicked', () => { it('Should change username if `Save` button is clicked', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('quincy', { force: true }); .type('quincy', { force: true });
@ -149,6 +164,7 @@ describe('Username input field', () => {
}); });
it('Should change username with uppercase characters if `Save` button is clicked', () => { it('Should change username with uppercase characters if `Save` button is clicked', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('Quincy', { force: true }); .type('Quincy', { force: true });
@ -162,6 +178,7 @@ describe('Username input field', () => {
}); });
it('Should show flash message showing username has been updated', () => { it('Should show flash message showing username has been updated', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('nhcarrigan', { force: true }); .type('nhcarrigan', { force: true });
@ -181,6 +198,7 @@ describe('Username input field', () => {
}); });
it('Should be able to close the shown flash message', () => { it('Should be able to close the shown flash message', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('bjorno', { force: true }); .type('bjorno', { force: true });
@ -197,6 +215,7 @@ describe('Username input field', () => {
}); });
it('Should change username if enter is pressed', () => { it('Should change username if enter is pressed', () => {
goToSettings();
cy.get('@usernameInput') cy.get('@usernameInput')
.clear({ force: true }) .clear({ force: true })
.type('symbol', { force: true }); .type('symbol', { force: true });

View File

@ -33,14 +33,7 @@
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => {}); // Cypress.Commands.overwrite('visit', (originalFn, url, options) => {});
Cypress.Commands.add('login', () => { Cypress.Commands.add('login', () => {
cy.visit('/'); cy.visit('http://localhost:3000/signin');
cy.contains("Get started (it's free)").click();
cy.location({ timeout: 10000 }).should(loc => {
// I'm not 100% sure why logins get redirected to /learn/ via 301 in
// development, but not in production, but they do. Hence to make it easier
// work on tests, we'll just allow for both.
expect(loc.pathname).to.match(/^\/learn\/?$/);
});
cy.contains('Welcome back'); cy.contains('Welcome back');
}); });
@ -60,7 +53,6 @@ Cypress.Commands.add('toggleAll', () => {
}); });
Cypress.Commands.add('resetUsername', () => { Cypress.Commands.add('resetUsername', () => {
cy.login();
cy.visit('/settings'); cy.visit('/settings');
cy.get('@usernameInput') cy.get('@usernameInput')

View File

@ -4635,7 +4635,7 @@ module.exports = {
rand: 0.6126749173148205, rand: 0.6126749173148205,
theme: 'default', theme: 'default',
profileUI: { profileUI: {
isLocked: true, isLocked: false,
showAbout: true, showAbout: true,
showCerts: true, showCerts: true,
showDonation: true, showDonation: true,