Skip to content

Commit

Permalink
test: add e2e test suite with cypress (#314)
Browse files Browse the repository at this point in the history
* test: add e2e test suite with cypress

* Fix for failing unit tests (#315)

Co-authored-by: Andrea Sonny <andreasonny83@gmail.com>
  • Loading branch information
dkundel and andreasonny83 committed Jan 7, 2022
1 parent e2bf0b0 commit e9ac22a
Show file tree
Hide file tree
Showing 15 changed files with 1,414 additions and 93 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
/docs
/docs
**/cypress/
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ coverage/
package-lock.json
.DS_Store
**/.env
.turbo
cypress/screenshots/
cypress/videos/
**/cypress/screenshots/
**/cypress/videos/
7 changes: 7 additions & 0 deletions _helpers/test-suite/cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const cypress = require('cypress');

async function runTests(cypressConfig) {
return cypress.run(cypressConfig);
}

module.exports = { runTests };
50 changes: 50 additions & 0 deletions _helpers/test-suite/e2e-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const { fsHelpers } = require('@twilio-labs/serverless-api');

async function getLocalTestServerConfig(projectPath, port, env) {
return {
baseDir: __dirname,
env,
port,
url: `localhost:${port}`,
detailedLogs: false,
live: false,
logs: false,
legacyMode: false,
appName: 'test',
forkProcess: false,
enableDebugLogs: false,
routes: await fsHelpers.getListOfFunctionsAndAssets(projectPath, {
functionsFolderNames: ['functions'],
assetsFolderNames: ['assets'],
}),
};
}

function getCypressConfig(projectPath, baseUrl, cypressConfig) {
return {
config: {
baseUrl,
video: false,
screenshotOnRunFailure: false,
},
// spec: join('e2e', 'hello-world.spec.js'),
project: projectPath,
configFile: false,
...cypressConfig,
};
}

async function getConfig(projectPath, env, cypressOptions = {}) {
const { default: getPort, portNumbers } = await import('get-port');

const port = await getPort({ port: portNumbers(8000, 8300) });

const baseUrl = `http://localhost:${port}`;

return {
cypress: getCypressConfig(projectPath, baseUrl, cypressOptions),
serverless: await getLocalTestServerConfig(projectPath, port, env),
};
}

module.exports = { getConfig };
32 changes: 32 additions & 0 deletions _helpers/test-suite/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { getConfig } = require('./e2e-config');
const { startLocalTestServer } = require('./serverless');
const { runTests } = require('./cypress');

async function runE2eTestSuite({ baseDir, env, cypressOptions } = {}) {
const config = await getConfig(
baseDir || process.cwd(),
env || {},
cypressOptions
);

try {
console.log('>>> Start local server');
const localDevServer = await startLocalTestServer(config.serverless);
console.log('>>> Run Cypress Tests');
const result = await runTests(config.cypress);
console.log('>>> Shutdown local server');
localDevServer.close();
if (result.status === 'failed' || result.totalFailed > 0) {
// eslint-disable-next-line no-process-exit
process.exit(1);
}
} catch (err) {
console.error(err);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
}

module.exports = {
runE2eTestSuite,
};
21 changes: 21 additions & 0 deletions _helpers/test-suite/serverless.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// eslint-disable-next-line import/no-unresolved
const { LocalDevelopmentServer } = require('@twilio/runtime-handler/dev');

function listen(app, port) {
return new Promise((resolve) => {
const server = app.listen(port, () => {
resolve(server);
});
});
}

async function startLocalTestServer(serverlessConfig) {
const functionsServer = new LocalDevelopmentServer(
serverlessConfig.port,
serverlessConfig
);

return listen(functionsServer.getApp(), serverlessConfig.port);
}

module.exports = { startLocalTestServer };
54 changes: 52 additions & 2 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Every Quick Deploy app has a version field in its `package.json` that follows [s

## Testing

### Testing the functionality of your new template locally
### Manually testing the functionality of your new template locally

1. Make sure you have the [Twilio CLI](https://www.twilio.com/docs/twilio-cli/quickstart) installed.

Expand All @@ -128,7 +128,7 @@ twilio serverless:start
twilio serverless:start --load-local-env
```

### Running automated tests
### Running automated unit tests

The tests are written using [Jest](https://jestjs.io/). You can run the test suite by running:

Expand All @@ -148,6 +148,56 @@ or alternatively:
npx jest --watch
```

### E2E tests using Cypress

#### Creating your first tests

1. Add an `e2e.js` file to your template with the following content:

```js
const { runE2eTestSuite } = require("../_helpers/test-suite");

runE2eTestSuite({
env: {
// put any environment variables for Twilio Functions here
}
})
```

You can use the object to also define custom Cypress configuration options.

2. Create a directory `cypress/integration` inside your template directory and add your Cypress test files there.

3. In the `package.json` of your template add the following:

```diff
{
"version": "1.0.0",
"private": true,
- "dependencies": {}
+ "dependencies": {},
+ "scripts": {
+ "e2e": "node e2e.js"
+ }
}
```

4. In the project root `package.json` add your template name to the `workspaces` array. For example:

```diff
"workspaces": [
"hello-world",
+ "my-template"
]
}
```

#### Running your E2E test suite

If you only want to run your own template, in the template directory run `npm run e2e`.

To run all E2E test suites, run in the root `npm run e2e`. This might take a while.

#### Fix any repository verification test failures

The majority of the test failures you will see from an `npm test` run will be in the unit tests you have written for your app. Occasionally, you may see a failure that originates in `all-templates.test.js`, which contains a suite of verifications that run against the entire `function-templates` codebase and help ensure that your code will successfully deploy once merged into the repository. Common failure cases for this test suite are:
Expand Down
12 changes: 12 additions & 0 deletions hello-world/cypress/integration/hello-world.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
describe('GET /hello-world', () => {
it('returns valid hello world', () => {
cy.request({
method: 'GET',
url: '/hello-world',
headers: {},
failOnStatusCode: false,
}).then((response) => {
expect(response.body).to.eq('Hello world!');
});
});
});
22 changes: 22 additions & 0 deletions hello-world/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// 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)

/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};
25 changes: 25 additions & 0 deletions hello-world/cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// ***********************************************
// 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) => { ... })
20 changes: 20 additions & 0 deletions hello-world/cypress/support/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands';

// Alternatively you can use CommonJS syntax:
// require('./commands')
7 changes: 7 additions & 0 deletions hello-world/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { runE2eTestSuite } = require('../_helpers/test-suite');

runE2eTestSuite({
env: {
// put any environment variables for Twilio Functions here
},
});
5 changes: 4 additions & 1 deletion hello-world/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"version": "1.0.0",
"private": true,
"dependencies": {}
"dependencies": {},
"scripts": {
"e2e": "node e2e.js"
}
}
Loading

0 comments on commit e9ac22a

Please sign in to comment.