Gleb Bahmutov
VP of Engineering @ Cypress.io
Graeme Harvey
Engineering Manager, Automation Platform
@ PlanGrid (Autodesk Construction Solutions)
Brendan Drew
Tech Lead, Automation Platform
@ PlanGrid (Autodesk Construction Solutions)
Used on over one million projects around the world, PlanGrid is the first construction productivity software that allows contractors and owners in commercial, heavy civil, and other industries to work and collaborate from anywhere.
Q & A: use Sli.do event code #cyplangrid
Back in 2018...
Not well understood by dev team
Gherkin layer of abstraction
Python Behave framework
Often skipped upon failure
To provide a testing framework familiar and easy to use for frontend developers, allowing them to write, run and maintain tests without increasing their workload significantly.
Webdriver.io
TestCafe
Cypress.io
Free market model
Teams can use whatever tools they want
Teams are empowered to build, test, release however they want
Suggested tools, but no mandates (with some exceptions)
Tools often chosen based on senior members' previous experience
Jest vs mocha
Sinon vs nock
Etc.
Make the easy way the right way
// package.json
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"lint": "react-scripts lint",
"tsc": "react-scripts tsc",
"publish": "react-scripts publish",
"format": "react-scripts format",
"serve:build": "react-scripts serve:build",
"cypress": "react-scripts cypress",
"cypress:build": "react-scripts cypress:build",
"inspect": "react-scripts inspect"
},
> npm run cypress
> npm run cypress:build
💡 Cypress team's tip: create CRA apps using our official template
if (process.env.CI === 'true') {
cypress
.run(args)
.then(results => {
const testsPassed = results.totalFailed === 0;
console.log(
`Cypress tests ${testsPassed
? chalk.green('PASSED')
: chalk.red('FAILED')}. Result details:`
);
console.log(results);
process.exit(testsPassed ? 0 : 1);
})
.catch(ex => {
console.error(
chalk.red('Cypress encountered an error and was unable to complete:')
);
console.error(ex);
process.exit(1);
});
} else {
cypress.open(args);
}
// Jenkinsfile
@Library("web-build-tools") _
stage("Checkout") {
GitClean()
checkout scm
}
runFullBuild(
projectType: "app"
)
// Jenkinsfile
@Library("web-build-tools") _
stage("Checkout") {
GitClean()
checkout scm
}
def cypressConfig = [
additionalRunVars: "BROWSER=chrome"
]
runFullBuild(
projectType: "app",
cypressConfig: cypressConfig
)
Tests need to run fast
Tests need to be able to run regardless of the target environment
Test fixes must be checked in with the code that broke them
Teams had tests
The tests worked
Lots of:
setup in the UI
long running tests
some
kind of
describe("with feature flag enabled on project", () => {
before(() => {
cy.setFeatureFlag(FEATURE_FLAG, projectUid);
cy.visit(`/projects/${projectUid}/transmittals/${DUMMY_TRANSMITTAL_UID}`);
});
after(() => {
cy.resetFeatureFlag(FEATURE_FLAG, projectUid);
});
it("should show project transmittal", () => {
cy.getByDataQa("NewProjectTransmittal").should("be.visible");
cy.getByDataQa("NewProjectTransmittalTitle")
.should("be.visible")
.should("contain.text", "New Transmittal");
});
});
Write smaller, isolated, targeted tests
Use APIs for setup and teardown.
UI test ONLY what’s under test
As more and more teams onboard:
Everyone needs to log in
Most tests need a project to work with
Many tests need documents in a project
Some tests need to manipulate feature flags
Easier to maintain if everyone performs these actions the same way
Benefit: Broken functionality gets fixed quickly and for everyone
a principle of extreme programming (XP) that states a programmer should not add functionality until deemed necessary.
...we started with
createUser() & login()
> npm install @plangrid-private/acs-cypress
> npx acs-cypress connect
// cypress/plugins/index
const acsPlugin = require("@plangrid-private/acs-cypress/dist/plugins/acsPlugin");
module.exports = (on, config) => {
return acsPlugin.plugin(on, config);
};
// cypress/support/index
import "@plangrid-private/acs-cypress";
Tests should run in any environment with no manual setup required
{
"users": [
{
"user_id": "5e0f9cdb154987db154d2c85",
"first_name": "Test",
"last_name": "User",
"email": "testuser@example.com",
"sheet_count": 0,
"plan": "inf",
"password": "--hidden--",
"apiToken": "--hidden--",
"stack": "dev",
"url": "http://localhost:3000",
"projects": [
{
"template": "threesheet",
"projectUid": "b75900a3",
"stack": "dev",
"returnProject": false
}
],
"projectUid": "b75900a3"
}
],
"projects": [
{
"template": "threesheet",
"projectUid": "b75900a3",
"stack": "dev",
"returnProject": false
}
],
"orgs": []
}
* To prevent time consuming operations projects can be pulled
from pre-populated pool
LaunchDarkly integration gives tests control over feature flag settings
Feature flags control is required to move away from static fixture data
describe("with feature flag enabled on project", () => {
const FEATURE_FLAG = "MY_FF";
before(() => {
cy.setFeatureFlag(FEATURE_FLAG, projectUid);
});
after(() => {
cy.resetFeatureFlag(FEATURE_FLAG, projectUid);
});
}
Frontend repos share an internal API client library
We leverage the same library to make backend requests
No need to maintain requests across tests
they stay in sync with the frontend
import { capiClient } from "@plangrid-private/web-api-clients";
// In your test
cy.capi(capiClient.createProject, { name: name }).then(project => {
// Do something with your new project
});
💡 Cypress team's tip: for strictly API tests you can use
💡 Cypress team's tip: test retries are coming to the Cypress core, see issue #1313
// Jenkinsfile
@Library("web-build-tools") _
stage("Checkout") {
GitClean()
checkout scm
}
def cypressConfig = [
additionalRunVars: "BROWSER=chrome",
recordKey: "r3c0rd-k3y-f4k3-r3c0rdk3y"
]
runFullBuild(
projectType: "app",
cypressConfig: cypressConfig
)
// cypress.json
{
"projectId": "f4k31d"
}
Custom commands include clear log output using Cypress' logger
Default:
Custom:
Matches verbiage and style of default commands
cy.capi() Custom Command:
cy.get() Default Command:
$ npm run cypress
> react-scripts cypress
Loading environment for APP=cypress STACK=dev
Loading environment from .env
Loading environment from .env.stack.dev
CI: false
DEBUG: error
SHARED_SECRET_PASSWORD: -hidden-
MAILOSAUR_SERVER: -hidden-
MAILOSAUR_API_KEY: -hidden-
DATADOG_API_KEY: -hidden-
DATADOG_APP_KEY: -hidden-
SALESFORCE_ACCOUNT_ID: -hidden-
GRIDBOY_ADMIN: -hidden-
LAUNCHDARKLY_ACCESS_TOKEN: -hidden-
..... etc
💡 Cypress team's tip: find this and similar plugins at https://on.cypress.io/plugins#reporting
Parallelization (In Progress)
Allow a cypressConfig value for numMachines
Mock API server to avoid environment instability issues
// Jenkinsfile
@Library("web-build-tools") _
stage("Checkout") {
GitClean()
checkout scm
}
def cypressConfig = [
additionalRunVars: "BROWSER=chrome",
recordKey: "a5a19008-e622-b014-d966fdacc667",
numMachines: 5
]
runFullBuild(
projectType: "app",
cypressConfig: cypressConfig
)
Unscalable maintenance is the death of progress