Skip to content

Commit

Permalink
Overall improvements and new features (GraphCMS) (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadorequest authored Dec 20, 2020
1 parent 94d618b commit 5ad05e8
Show file tree
Hide file tree
Showing 178 changed files with 6,049 additions and 1,088 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.next
cypress
src/**/*.test*
src/gql/**
src/propTypes/**
Expand Down
38 changes: 38 additions & 0 deletions cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,44 @@ Read more about [the folder structure](https://docs.cypress.io/guides/core-conce
- `screenshot`/`videos`: Will contain assets when tests fail, useful during development. When tests are executed by Github Actions, you'll find those assets under the "Artifacts" section. (e.g: https://github.com/UnlyEd/next-right-now/runs/862302266)
- `fixtures`: Fixtures are used as external pieces of static data that can be used by your tests. (We've kept the fixtures installed during the Cypress initial install)

## Cypress config files

The files `cypress/config-*` are used for different purposes.

- `config-customer-ci-cd.json`: This file is a mock config file used by CI/CD GitHub Actions by the workflows `deploy-vercel-production.yml` and `deploy-vercel-staging.yml`.
The `baseUrl` is a fake value (required by Cypress, but not used) which is replaced at runtime by the real `baseUrl` which is a dynamic Vercel deployment url.
- `config-development.json`: This file is only used when running `yarn e2e:run` and `yarn e2e:open` locally.
It uses `baseUrl=http://localhost:8888` which is where our local server is running. It's only meant for local testing
- `config-$CUSTOMER_REF.json`: This file is only used when running `yarn deploy:$CUSTOMER_REF` locally. _It is not used by CI/CD workflows._

## Tests ordering

[Sanity Checks](./integration/app/_sanity/README.md) are executed first. Then, tests are executed by their folder/file alphabetical order by default.

## Resources about how to write tests better

- [[MUST WATCH!] Best Practices by the Author (2018) - 27mn](https://docs.cypress.io/examples/examples/tutorials.html#Best-Practices)
- [Organize tests by type of devices (mobile/desktop)](https://docs.cypress.io/api/commands/viewport.html#Width-Height)
- [Run tests on multiple subdomains](https://docs.cypress.io/faq/questions/using-cypress-faq.html#Can-I-run-the-same-tests-on-multiple-subdomains)
- [Detect if Cypress is running](https://docs.cypress.io/faq/questions/using-cypress-faq.html#Is-there-any-way-to-detect-if-my-app-is-running-under-Cypress)
- [Can my tests interact with Redux / Vuex data store? (AKA "Dynamic testing")](https://docs.cypress.io/faq/questions/using-cypress-faq.html#Can-my-tests-interact-with-Redux-Vuex-data-store)
- [Check a custom property from the `window` object](https://docs.cypress.io/api/commands/window.html#Check-a-custom-property)
- [Dynamic tests](https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/fundamentals__dynamic-tests)
- [Filters and data-driven tests](https://docs.cypress.io/examples/examples/tutorials.html#7-Filters-and-data-driven-tests)
- [cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui/transaction-feeds.spec.ts)

## Officiel Cypress recommandations

> We see organizations starting with Cypress by placing end-to-end tests in a separate repo.
> This is a great practice that allows someone on the team to prototype a few tests and evaluate Cypress within minutes.
> As the time passes and the number of tests grows, we strongly suggest moving end-to-end tests to live right alongside your front end code.
>
> This brings many benefits:
> - engages developers in writing end-to-end tests sooner
> - keeps tests and the features they test in sync
> - tests can be run every time the code changes
> - allows code sharing between the application code and the tests (like selectors)
_[Source](https://docs.cypress.io/faq/questions/using-cypress-faq.html#What-are-your-best-practices-for-organizing-tests)_

[Cypress releases "Real World App" (RWA) - Blog post](https://www.cypress.io/blog/2020/06/11/introducing-the-cypress-real-world-app/)
2 changes: 1 addition & 1 deletion cypress/config-customer-ci-cd.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"//": "This file is used by CI/CD GitHub Actions and not meant to be used locally",
"//": "This file is used by CI/CD GitHub Actions (staging + production) and not meant to be used locally",
"baseUrl": "https://nrn-customer.vercel.app",
"projectId": "4dvdog",
"screenshotsFolder": "cypress/screenshots/customer",
Expand Down
3 changes: 2 additions & 1 deletion cypress/config-development.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"screenshotsFolder": "cypress/screenshots/development",
"videosFolder": "cypress/videos/development",
"env": {},
"ignoreTestFiles": "*.md"
"ignoreTestFiles": "*.md",
"experimentalComponentTesting": true
}
1 change: 1 addition & 0 deletions cypress/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="cypress" />
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const baseUrl = Cypress.config().baseUrl;

describe('Sanity checks > Domain', {
retries: {
runMode: 2, // Allows 2 retries (for a total of 3 attempts) to reduce the probability of failing the whole tests suite because Vercel hasn't finished to deploy yet (which makes Cypress fail by trying to test the Vercel "waiting page", instead of our app)
}
// retries: {
// runMode: 2, // Allows 2 retries (for a total of 3 attempts) to reduce the probability of failing the whole tests suite because Vercel hasn't finished to deploy yet (which makes Cypress fail by trying to test the Vercel "waiting page", instead of our app)
// }
}, () => {
/*
* Visits the home page before any test
Expand All @@ -12,7 +12,7 @@ describe('Sanity checks > Domain', {
cy.visit('/en');
});

it('should be running on the right domain', () => {
it(`should be running on the domain "${baseUrl}"`, () => {
cy.url().then((url) => {
cy.log(`Expected to be running on:`);
cy.log(baseUrl);
Expand All @@ -22,3 +22,5 @@ describe('Sanity checks > Domain', {
});
});
});

export {};
31 changes: 31 additions & 0 deletions cypress/integration/app/_sanity/2-customer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Customer } from '../../../../src/types/data/Customer';
import { CYPRESS_WINDOW_NS } from '../../../../src/utils/testing/cypress';

describe('Sanity checks > Browser data', () => {
/**
* Visits the home page before any test.
*/
before(() => {
cy.visit('/en');
});

/**
* Prepare aliases before each test. (they're destroyed at the end of each test)
*/
beforeEach(() => {
cy.prepareDOMAliases();
});

it(`should have "window.${CYPRESS_WINDOW_NS}.dataset" defined`, () => {
cy.get('@dataset').then((dataset) => {
assert.isDefined(dataset);
expect(Object.keys(dataset).length).to.be.greaterThan(0);
});
});

it(`should have "window.${CYPRESS_WINDOW_NS}.customer" defined`, () => {
cy.get<Customer>('@customer').then((customer: Customer) => {
assert.isDefined(customer.label);
});
});
});
23 changes: 0 additions & 23 deletions cypress/integration/app/common/footer.js

This file was deleted.

48 changes: 48 additions & 0 deletions cypress/integration/app/common/footer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Customer } from '../../../../src/types/data/Customer';

const baseUrl = Cypress.config().baseUrl;

describe('Common > Footer section', () => {
/**
* Visits the home page before any test.
*/
before(() => {
cy.visit('/en');
});

/**
* Prepare aliases before each test. (they're destroyed at the end of each test)
*/
beforeEach(() => {
cy.prepareDOMAliases();
});

it('should have the Unly logo in the footer', () => {
cy.get('#footer-logo-unly-brand').should('have.length', 1);
});

it('should have the customer logo in the footer', () => {
cy.get('#footer-logo').should('have.length', 1);
});

it('should display the i18n button to change language', () => {
cy.get<Customer>('@customer').then((customer: Customer) => {
const availableLanguagesCount = 2;
cy.log(`Available language(s): ${availableLanguagesCount}`);

if (availableLanguagesCount > 1) {
it('should have a button to change the language which changes the language upon click', () => {
cy.get('#footer-btn-change-locale').should('have.length', 1).click({ force: true });
cy.url().should('eq', `${baseUrl}/fr`);
});
} else {
it('should not have a button to change the language', () => {
cy.get('#footer-btn-change-locale').should('not.have.length', 1);
});
}
});
});

});

export {};
43 changes: 0 additions & 43 deletions cypress/integration/app/common/nav.js

This file was deleted.

35 changes: 35 additions & 0 deletions cypress/integration/app/common/nav.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Customer } from '../../../../src/types/data/Customer';

const baseUrl = Cypress.config().baseUrl;

describe('Common > Nav section', () => {
/**
* Visits the home page before any test.
*/
before(() => {
cy.visit('/en');
});

/**
* Prepare aliases before each test. (they're destroyed at the end of each test)
*/
beforeEach(() => {
cy.prepareDOMAliases();
});

it('should have 3 links in the navigation bar', () => {
cy.get('#nav .navbar-nav > .nav-item').should('have.length', 5);
});

it('should have a link in the navbar that redirects to the home page', () => {
cy.get<Customer>('@customer').then((customer: Customer) => {
const isPageInEnglish = true;
cy.get('#nav-link-home')
.should('have.text', isPageInEnglish ? 'Home' : 'Accueil')
.click();
cy.url({ timeout: 10000 }).should('eq', `${baseUrl}/${isPageInEnglish ? 'en' : 'fr'}`);
});
});
});

export {};
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
const baseUrl = Cypress.config().baseUrl;

describe('Index page', () => {
/*
* Visits the home page before any test
*/
/**
* Visits the home page before any test.
*/
before(() => {
cy.visit('/en');
});

/**
* Prepare aliases before each test. (they're destroyed at the end of each test)
*/
beforeEach(() => {
cy.prepareDOMAliases();
});

it('should display a main title', () => {
cy.get('h1').should('have.length', 1).should('have.text', 'Next Right Now Demo');
});
});

export {};
3 changes: 3 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)

/// <reference types="cypress" />

module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
return config;
}
5 changes: 5 additions & 0 deletions cypress/support/commands.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare namespace Cypress {
interface cy extends Chainable<undefined> {
prepareDOMAliases: () => Chainable<Element>;
}
}
40 changes: 24 additions & 16 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,27 @@
// 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) => { ... })

import { CYPRESS_WINDOW_NS } from '../../src/utils/testing/cypress';

/**
* Prepare DOM aliases by fetching the customer data from the browser window and aliasing them for later use.
*
* @example cy.prepareDOMAliases();
*/
Cypress.Commands.add('prepareDOMAliases', () => {
return cy.window().then((window) => {
cy.get('.page-wrapper').then(() => { // Wait for the DOM element to be created by Next.js before trying to read any dynamic data from the "window" object
cy.log(`window[${CYPRESS_WINDOW_NS}]`, window[CYPRESS_WINDOW_NS]);

const {
customer,
dataset,
} = window[CYPRESS_WINDOW_NS];

// Use aliases to make our variables reusable across tests - See https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Sharing-Context
cy.wrap(customer).as('customer');
cy.wrap(dataset).as('dataset');
});
});
});
3 changes: 2 additions & 1 deletion cypress/support/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
// See https://dev.to/cuichenli/how-do-i-setup-my-nextjs-development-environment-2kao
import 'cypress-react-unit-test/support';
import './commands';

// See https://docs.cypress.io/api/events/catalog-of-events.html#Uncaught-Exceptions
Expand Down
16 changes: 16 additions & 0 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../tsconfig.json",
"include": [
"./**/*.ts*"
],
"exclude": [],
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"types": [
"cypress"
],
"sourceMap": false,
"isolatedModules": true
}
}
Loading

1 comment on commit 5ad05e8

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.