feat(test, e2e) test suit for cypress (#42138)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -1,8 +1,5 @@
|
||||
name: Cypress
|
||||
name: Cypress - Pull-request
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'renovate/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
91
.github/workflows/cypress-push.yml
vendored
Normal file
91
.github/workflows/cypress-push.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
name: Cypress - Push
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'renovate/**'
|
||||
|
||||
jobs:
|
||||
cypress-run:
|
||||
name: Test
|
||||
runs-on: ubuntu-18.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
containers: [1, 2, 3, 4, 5, 6]
|
||||
browsers: [chrome, firefox]
|
||||
node-version: [14.x]
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:3.6.19
|
||||
ports:
|
||||
- 27017:27017
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- 1025:1025
|
||||
|
||||
steps:
|
||||
# We use .npmrc to set the default version to 0, and prevents download during development.
|
||||
# This installs it specifically in the CI runs.
|
||||
- name: Set Action Environment Variables
|
||||
run: |
|
||||
echo "CYPRESS_RECORD_KEY=${{ secrets.CYPRESS_RECORD_KEY }}" >> $GITHUB_ENV
|
||||
echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CYPRESS_INSTALL_BINARY=7.1.0" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout Source Files
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Set freeCodeCamp Environment Variables
|
||||
run: cp sample.env .env
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run ensure-env
|
||||
npm run build:curriculum
|
||||
|
||||
- name: Seed Database
|
||||
run: npm run seed
|
||||
|
||||
- name: Generate fixture data
|
||||
run: npm run precypress:gen:fixtures
|
||||
|
||||
- name: Generate Specfiles for challenges
|
||||
run: npm run precypress:gen:test
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v2
|
||||
with:
|
||||
parallel: ${{ env.CYPRESS_RECORD_KEY != 0 }}
|
||||
group: ${{ matrix.browsers }}
|
||||
record: ${{ env.CYPRESS_RECORD_KEY != 0 }}
|
||||
build: npm run build
|
||||
# this should mirror the production build, but for now we're using gatsby
|
||||
# serve instead (the npm script serve:client needs updating!)
|
||||
start: npm run start-ci
|
||||
wait-on: http://localhost:8000
|
||||
# the site builds in about 8 minutes, so there is currently 12 minutes of time
|
||||
# left for testing.
|
||||
wait-on-timeout: 1200
|
||||
config: baseUrl=http://localhost:8000
|
||||
browser: ${{ matrix.browsers }}
|
||||
headless: true
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -116,6 +116,10 @@ coverage
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
|
||||
### Cypress generated fixtures and tests ###
|
||||
cypress/fixtures/path-data
|
||||
cypress/integration/challenge-tests/blocks
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
@ -179,3 +183,4 @@ api-server/lib/*
|
||||
curriculum/dist
|
||||
curriculum/build
|
||||
client/static/_redirects
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"projectId": "ke77ns",
|
||||
"baseUrl": "http://localhost:8000",
|
||||
"retries": 4
|
||||
"retries": 4,
|
||||
"videoUploadOnPasses": false
|
||||
}
|
||||
|
0
cypress/integration/challenge-tests/blocks/.gitkeep
Normal file
0
cypress/integration/challenge-tests/blocks/.gitkeep
Normal file
@ -1,17 +1,5 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
/* eslint-disable no-unused-vars */
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
module.exports = function (on, config) {
|
||||
// configure plugins here
|
||||
};
|
||||
|
@ -1,37 +1,4 @@
|
||||
/* global cy Cypress*/
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => {});
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add(
|
||||
// 'drag',
|
||||
// { prevSubject: 'element' },
|
||||
// (subject, options) => {}
|
||||
// );
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add(
|
||||
// 'dismiss',
|
||||
// { prevSubject: 'optional' },
|
||||
// (subject, options) => {}
|
||||
// );
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => {});
|
||||
/* global cy Cypress */
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.visit('/');
|
||||
@ -52,3 +19,50 @@ Cypress.Commands.add('resetUsername', () => {
|
||||
|
||||
cy.contains('Account Settings for developmentuser').should('be.visible');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('testChallenges', () => {
|
||||
// Test Meta tags
|
||||
cy.get('head meta[charset=utf-8]');
|
||||
cy.get('head meta[name=description]').should('have.attr', 'content');
|
||||
|
||||
// Test breadcrumbs
|
||||
cy.get('.breadcrumb-right').should('have.attr', 'href');
|
||||
cy.get('.ellipsis').should('be.visible');
|
||||
cy.get('.breadcrumb-left').should('have.attr', 'href');
|
||||
cy.get('.breadcrumb-left').should('be.visible', 'href');
|
||||
|
||||
cy.get('body').should('be.visible');
|
||||
|
||||
// Challenge content
|
||||
cy.get('.challenge-title').should('be.visible');
|
||||
cy.get('#description').should('be.visible');
|
||||
|
||||
// Monaco editor
|
||||
cy.get('.react-monaco-editor-container')
|
||||
.click()
|
||||
.focused()
|
||||
.type('<h1> Hello world! </h1>');
|
||||
|
||||
// Ensure that there are test
|
||||
cy.get('.challenge-test-suite').children().its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
// This command can be used to test projects and back-end challenges
|
||||
Cypress.Commands.add('testProjectsAndBackend', () => {
|
||||
// Test breadcrumbs
|
||||
cy.get('.breadcrumb-right').should('have.attr', 'href');
|
||||
cy.get('.ellipsis').should('be.visible');
|
||||
cy.get('.breadcrumb-left').should('have.attr', 'href');
|
||||
cy.get('.breadcrumb-left').should('be.visible', 'href');
|
||||
|
||||
// Challenge content
|
||||
cy.get('.challenge-title').should('be.visible');
|
||||
cy.get('#description').should('be.visible');
|
||||
|
||||
// Shoud be possible to submit solution
|
||||
cy.get('input[name=solution]')
|
||||
.click()
|
||||
.type('https://codepen.io/foobar/full/RKRbwL');
|
||||
|
||||
cy.get('button[type=submit]').first().click();
|
||||
});
|
||||
|
122
generate-fixture-data.js
Normal file
122
generate-fixture-data.js
Normal file
@ -0,0 +1,122 @@
|
||||
const { writeFileSync, mkdirSync } = require('fs');
|
||||
const getChallenge = require('./curriculum/getChallenges');
|
||||
const { challengeTypes } = require('./client/utils/challengeTypes');
|
||||
const path = require('path');
|
||||
|
||||
function getCurriculum() {
|
||||
return getChallenge.getChallengesForLang('english');
|
||||
}
|
||||
|
||||
function initCurriculum() {
|
||||
const superblocks = [
|
||||
'apis-and-microservices',
|
||||
'data-visualization',
|
||||
'front-end-libraries',
|
||||
'javascript-algorithms-and-data-structures',
|
||||
'responsive-web-design'
|
||||
];
|
||||
|
||||
const init = getCurriculum();
|
||||
|
||||
init.then(curriculum => {
|
||||
superblocks.forEach(superblock => {
|
||||
console.log(`creating pathdata for ${superblock} now`);
|
||||
|
||||
const blocks = Object.keys(curriculum[superblock]['blocks']);
|
||||
|
||||
createPaths(curriculum, superblock, blocks);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createDirs() {
|
||||
mkdirSync(path.join(__dirname, '/cypress/fixtures/path-data'));
|
||||
mkdirSync(path.join(__dirname, '/cypress/fixtures/path-data/challenges'));
|
||||
mkdirSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
'/cypress/fixtures/path-data/projects-and-back-challenges'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
createDirs();
|
||||
initCurriculum();
|
||||
|
||||
function createPaths(curriculum, superblock, blocks) {
|
||||
let challengeObj = { blocks: {} };
|
||||
let challengeObj2 = { blocks: {} };
|
||||
|
||||
let challengePaths;
|
||||
|
||||
// Specifies which challenge type has an editor
|
||||
const typeHasEditor = [
|
||||
challengeTypes.html,
|
||||
challengeTypes.js,
|
||||
challengeTypes.bonfire,
|
||||
challengeTypes.modern
|
||||
];
|
||||
|
||||
blocks.forEach(block => {
|
||||
const challengeArr = curriculum[superblock]['blocks'][block]['challenges'];
|
||||
|
||||
challengePaths = challengeArr.map(challengePath => [
|
||||
`/learn/${superblock}/${block}/${challengePath['dashedName']}`,
|
||||
challengePath['challengeType']
|
||||
]);
|
||||
|
||||
// Make variables defined before accessing them when checking for challenge type
|
||||
|
||||
challengeObj['blocks'][block] = {};
|
||||
challengeObj2['blocks'][block] = {};
|
||||
|
||||
challengePaths.forEach(challengePath => {
|
||||
const challengeName = challengePath[0].split('/');
|
||||
|
||||
if (typeHasEditor.includes(challengePath[1])) {
|
||||
challengeObj['blocks'][block][challengeName[challengeName.length - 1]] =
|
||||
challengePath[0];
|
||||
} else {
|
||||
challengeObj2['blocks'][block][
|
||||
challengeName[challengeName.length - 1]
|
||||
] = challengePath[0];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove the objects if they are empty
|
||||
function cleanEmptyObjects(obj) {
|
||||
const getSize = function (obj) {
|
||||
let size = 0;
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) size++;
|
||||
}
|
||||
return size;
|
||||
};
|
||||
|
||||
for (let block in obj['blocks']) {
|
||||
if (getSize(obj['blocks'][block]) === 0) {
|
||||
delete obj['blocks'][block];
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(obj, null, 4);
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
`/cypress/fixtures/path-data/challenges/${superblock}.json`
|
||||
),
|
||||
cleanEmptyObjects(challengeObj)
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
`/cypress/fixtures/path-data/projects-and-back-challenges/${superblock}.json`
|
||||
),
|
||||
cleanEmptyObjects(challengeObj2)
|
||||
);
|
||||
}
|
91
generate-spec-files.js
Normal file
91
generate-spec-files.js
Normal file
@ -0,0 +1,91 @@
|
||||
const { readdirSync, readFileSync, writeFileSync } = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('Creating challenge specfiles...');
|
||||
|
||||
function createSpecFiles() {
|
||||
// Get blocks in directory
|
||||
|
||||
const challengesFiles = readdirSync(
|
||||
path.join(__dirname, '/cypress/fixtures/path-data/challenges')
|
||||
);
|
||||
|
||||
const projectsFiles = readdirSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
'/cypress/fixtures/path-data/projects-and-back-challenges'
|
||||
)
|
||||
);
|
||||
|
||||
const blockExist = readdirSync(
|
||||
path.join(__dirname, '/cypress/integration/challenge-tests/blocks')
|
||||
);
|
||||
|
||||
// Split the extensions
|
||||
|
||||
let blockInDir = [];
|
||||
blockExist.forEach(block => {
|
||||
blockInDir.push(block.split('.')[0]);
|
||||
});
|
||||
|
||||
function divider(files, project) {
|
||||
files.forEach(file => {
|
||||
let files = JSON.parse(
|
||||
readFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
`/cypress/fixtures/path-data/${
|
||||
project ? 'projects-and-back-challenges' : 'challenges'
|
||||
}/${file}`
|
||||
),
|
||||
'utf-8'
|
||||
)
|
||||
);
|
||||
|
||||
let challengeBlocks = Object.keys(files['blocks']);
|
||||
|
||||
challengeBlocks.forEach(block => {
|
||||
if (!blockInDir.includes(block)) {
|
||||
writeFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
`/cypress/integration/challenge-tests/blocks/${block}.js`
|
||||
),
|
||||
`/* global cy */
|
||||
const superBlockPath = require('../../../fixtures/path-data/${
|
||||
project ? 'projects-and-back-challenges' : 'challenges'
|
||||
}/${file}');
|
||||
|
||||
const blocks = Object.entries(superBlockPath['blocks']['${block}'])
|
||||
|
||||
for(const [challengeName , challengePath] of blocks){
|
||||
describe('loading challenge', () => {
|
||||
before(() => {
|
||||
cy.visit(challengePath)
|
||||
})
|
||||
|
||||
it('Challenge ' + challengeName + ' should work correctly', () => {
|
||||
${
|
||||
project
|
||||
? 'cy.testProjectsAndBackend(challengePath)'
|
||||
: 'cy.testChallenges(challengePath)'
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
divider(challengesFiles, false);
|
||||
divider(projectsFiles, true);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
createSpecFiles();
|
||||
|
||||
console.log('specfiles generated!');
|
@ -38,8 +38,11 @@
|
||||
"clean:root-deps": "shx rm -rf node_modules",
|
||||
"clean:server": "shx rm -rf ./api-server/lib",
|
||||
"precypress": "node ./cypress-install.js",
|
||||
"precypress:gen:test": "node ./generate-spec-files.js",
|
||||
"precypress:gen:fixtures": "node ./generate-fixture-data.js",
|
||||
"cypress": "cypress",
|
||||
"cypress:dev:run": "npm run cypress -- run",
|
||||
"cypress:dev:run": "npm run cypress -- run --spec \"cypress/integration/main-tests/**/*\"",
|
||||
"cypress:dev:run:full": "npm run cypress -- run --spec \"cypress/integration/**/*\"",
|
||||
"cypress:dev:watch": "npm run cypress -- open",
|
||||
"cypress:install": "cypress install && echo 'for use with ./cypress-install.js'",
|
||||
"cypress:install-build-tools": "sh ./cypress-install.sh",
|
||||
|
Reference in New Issue
Block a user