Skip to content

Commit 4f14f92

Browse files
committed
Cycpress integration testing
1 parent ff4eb3e commit 4f14f92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2432
-500
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM quay.io/coreos/tectonic-console-builder:v19 AS build
1+
FROM quay.io/coreos/tectonic-console-builder:v20 AS build
22

33
RUN mkdir -p /go/src/github.com/openshift/console/
44
ADD . /go/src/github.com/openshift/console/

Dockerfile.builder

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ RUN chmod 777 -R ${HOME}
2727

2828
RUN apt-get update \
2929
&& apt-get install --no-install-recommends -y -q \
30-
curl wget git unzip bzip2 jq
30+
curl wget git unzip bzip2 jq \
31+
libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
32+
# ^^ additional Cypress dependencies: https://docs.cypress.io/guides/guides/continuous-integration.html#Dependencies
3133

3234
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.17.0/bin/linux/amd64/kubectl && \
3335
chmod +x ./kubectl && \

README.md

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,22 @@ Run frontend tests:
185185

186186
### Integration Tests
187187

188+
#### Cypress
189+
190+
Cypress integration tests are run via [Cypress.io](https://www.cypress.io/).
191+
192+
Launch Cypress test runner:
193+
```
194+
cd frontend
195+
oc login ...
196+
yarn run test-cypress
197+
```
198+
199+
This will launch the Cypress test runner where you can run one or all cypress tests.
200+
201+
[**_More information on Console's Cypress usage_**](frontend/packages/integration-tests-cypress/README.md)
202+
#### Protractor
203+
188204
Integration tests are run in a headless browser driven by [protractor](http://www.protractortest.org/#/).
189205
Requirements include Chrome or Firefox, a working cluster, kubectl, and bridge itself (see building above).
190206
By default, it will look for Chrome in the system and use it, but if you want to use Firefox instead, set `BRIDGE_E2E_BROWSER_NAME` environment variable in your shell with the value `firefox`.
@@ -220,7 +236,6 @@ For macOS, you can use:
220236
```
221237
yarn run webdriver-update-macos
222238
```
223-
224239
#### How the Integration Tests Run in CI
225240

226241
The end-to-end tests run against pull requests using [ci-operator](https://github.com/openshift/ci-operator/).
@@ -245,7 +260,7 @@ If you don't want to run the entire e2e tests, you can use a different suite fro
245260
$ ./test-gui.sh <suite>
246261
```
247262

248-
#### Hacking Integration Tests
263+
##### Hacking Protractor Tests
249264

250265
To see what the tests are actually doing, it is posible to run in none `headless` mode by setting the `NO_HEADLESS` environment variable:
251266

@@ -265,7 +280,7 @@ To avoid skipping remaining portion of tests upon encountering the first failure
265280
$ NO_FAILFAST=true ./test-gui.sh <suite>
266281
```
267282

268-
##### Debugging Integration Tests
283+
##### Debugging Protractor Tests
269284

270285
1. `cd frontend; yarn run build`
271286
2. Add `debugger;` statements to any e2e test
@@ -275,6 +290,30 @@ $ NO_FAILFAST=true ./test-gui.sh <suite>
275290
6. Will break on any `debugger;` statements
276291
7. Pauses browser when not using `--headless` argument!
277292

293+
#### How the Integration Tests Run in CI
294+
295+
The end-to-end tests run against pull requests using [ci-operator](https://github.com/openshift/ci-operator/).
296+
The tests are defined in [this manifest](https://github.com/openshift/release/blob/master/ci-operator/jobs/openshift/console/openshift-console-master-presubmits.yaml)
297+
in the [openshift/release](https://github.com/openshift/release) repo and were generated with [ci-operator-prowgen](https://github.com/openshift/ci-operator-prowgen).
298+
299+
CI runs the [test-prow-e2e.sh](test-prow-e2e.sh) script, which runs the cypress tests and the protractor `e2e` test suite defined in [protractor.conf.ts](frontend/integration-tests/protractor.conf.ts).
300+
301+
You can simulate an e2e run against an existing 4.0 cluster with the following commands (replace `/path/to/install-dir` with your OpenShift 4.0 install directory):
302+
303+
```
304+
$ oc apply -f ./frontend/integration-tests/data/htpasswd-secret.yaml
305+
$ oc patch oauths cluster --patch "$(cat ./frontend/integration-tests/data/patch-htpasswd.yaml)" --type=merge
306+
$ export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')"
307+
$ export BRIDGE_KUBEADMIN_PASSWORD=$(cat "/path/to/install-dir/auth/kubeadmin-password")
308+
$ ./test-gui.sh e2e
309+
```
310+
311+
If you don't want to run the entire e2e tests, you can use a different suite from [protractor.conf.ts](frontend/integration-tests/protractor.conf.ts). For instance,
312+
313+
```
314+
$ ./test-gui.sh <suite>
315+
```
316+
278317
### Deploying a Custom Image to an OpenShift Cluster
279318

280319
Once you have made changes locally, these instructions will allow you to push

builder-run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ set -e
1111
# Without env vars:
1212
# ./builder-run.sh ./my-script --my-script-arg1 --my-script-arg2
1313

14-
BUILDER_IMAGE="quay.io/coreos/tectonic-console-builder:v19"
14+
BUILDER_IMAGE="quay.io/coreos/tectonic-console-builder:v20"
1515

1616
# forward whitelisted env variables to docker
1717
ENV_STR=()

frontend/integration-tests/tests/crud.scenario.ts

Lines changed: 6 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,17 @@ import { browser, $, $$, by, ExpectedConditions as until, Key, element } from 'p
44
import { safeLoad, safeDump } from 'js-yaml';
55
import * as _ from 'lodash';
66
import { execSync } from 'child_process';
7-
import { OrderedMap } from 'immutable';
87

98
import { appHost, testName, checkLogs, checkErrors } from '../protractor.conf';
109
import * as crudView from '../views/crud.view';
1110
import * as yamlView from '../views/yaml.view';
12-
import * as namespaceView from '../views/namespace.view';
1311
import * as createRoleBindingView from '../views/create-role-binding.view';
1412

1513
const K8S_CREATION_TIMEOUT = 15000;
1614

1715
describe('Kubernetes resource CRUD operations', () => {
1816
const testLabel = 'automatedTestName';
1917
const leakedResources = new Set<string>();
20-
const k8sObjs = OrderedMap<string, { kind: string; namespaced?: boolean }>()
21-
.set('pods', { kind: 'Pod' })
22-
.set('services', { kind: 'Service' })
23-
.set('serviceaccounts', { kind: 'ServiceAccount' })
24-
.set('secrets', { kind: 'Secret' })
25-
.set('configmaps', { kind: 'ConfigMap' })
26-
.set('persistentvolumes', { kind: 'PersistentVolume', namespaced: false })
27-
.set('storageclasses', { kind: 'StorageClass', namespaced: false })
28-
.set('ingresses', { kind: 'Ingress' })
29-
.set('cronjobs', { kind: 'CronJob' })
30-
.set('jobs', { kind: 'Job' })
31-
.set('daemonsets', { kind: 'DaemonSet' })
32-
.set('deployments', { kind: 'Deployment' })
33-
.set('replicasets', { kind: 'ReplicaSet' })
34-
.set('replicationcontrollers', { kind: 'ReplicationController' })
35-
.set('persistentvolumeclaims', { kind: 'PersistentVolumeClaim' })
36-
.set('statefulsets', { kind: 'StatefulSet' })
37-
.set('resourcequotas', { kind: 'ResourceQuota' })
38-
.set('limitranges', { kind: 'LimitRange' })
39-
.set('horizontalpodautoscalers', { kind: 'HorizontalPodAutoscaler' })
40-
.set('networkpolicies', { kind: 'NetworkPolicy' })
41-
.set('roles', { kind: 'Role' });
42-
const openshiftObjs = OrderedMap<string, { kind: string; namespaced?: boolean }>()
43-
.set('deploymentconfigs', { kind: 'DeploymentConfig' })
44-
.set('buildconfigs', { kind: 'BuildConfig' })
45-
.set('imagestreams', { kind: 'ImageStream' })
46-
.set('routes', { kind: 'Route' })
47-
.set('user.openshift.io~v1~Group', { kind: 'user.openshift.io~v1~Group', namespaced: false });
48-
const serviceCatalogObjs = OrderedMap<string, { kind: string; namespaced?: boolean }>().set(
49-
'clusterservicebrokers',
50-
{
51-
kind: 'servicecatalog.k8s.io~v1beta1~ClusterServiceBroker',
52-
namespaced: false,
53-
},
54-
);
55-
let testObjs = browser.params.openshift === 'true' ? k8sObjs.merge(openshiftObjs) : k8sObjs;
56-
testObjs =
57-
browser.params.servicecatalog === 'true' ? testObjs.merge(serviceCatalogObjs) : testObjs;
5818

5919
afterEach(() => {
6020
checkLogs();
@@ -63,9 +23,12 @@ describe('Kubernetes resource CRUD operations', () => {
6323

6424
afterAll(() => {
6525
const leakedArray: Array<string> = [...leakedResources];
66-
console.error(
67-
`Leaked ${leakedArray.length} resources out of ${testObjs.size}:\n${leakedArray.join('\n')}`,
68-
);
26+
if (!_.isEmpty(leakedArray)) {
27+
console.error(`Leaked ${leakedArray.length} resources\n${leakedArray.join('\n')}.`);
28+
} else {
29+
console.log('No resources leaked.');
30+
}
31+
6932
leakedArray
7033
.map((r) => JSON.parse(r) as { name: string; plural: string; namespace?: string })
7134
.filter((r) => r.namespace === undefined)
@@ -78,104 +41,6 @@ describe('Kubernetes resource CRUD operations', () => {
7841
});
7942
});
8043

81-
testObjs.forEach(({ kind, namespaced = true }, resource) => {
82-
describe(kind, () => {
83-
const name = `${testName}-${_.kebabCase(kind)}`;
84-
it('displays a list view for the resource', async () => {
85-
await browser.get(
86-
`${appHost}${
87-
namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'
88-
}/${resource}?name=${testName}`,
89-
);
90-
await crudView.isLoaded();
91-
});
92-
93-
if (namespaced) {
94-
it('has a working namespace dropdown on namespaced objects', async () => {
95-
await browser.wait(until.presenceOf(namespaceView.namespaceSelector));
96-
expect(namespaceView.namespaceSelector.getText()).toContain(testName);
97-
});
98-
} else {
99-
it('does not have a namespace dropdown on non-namespaced objects', async () => {
100-
expect(namespaceView.namespaceSelector.isPresent()).toBe(false);
101-
});
102-
}
103-
104-
it('displays a YAML editor for creating a new resource instance', async () => {
105-
await crudView.clickListPageCreateYAMLButton();
106-
const yamlLinkIsPresent = await crudView.createYAMLLink.isPresent();
107-
if (yamlLinkIsPresent) {
108-
await crudView.createYAMLLink.click();
109-
}
110-
await yamlView.isLoaded();
111-
112-
const content = await yamlView.getEditorContent();
113-
const newContent = _.defaultsDeep(
114-
{},
115-
{ metadata: { name, labels: { [testLabel]: testName } } },
116-
safeLoad(content),
117-
);
118-
await yamlView.setEditorContent(safeDump(newContent));
119-
});
120-
121-
it('creates a new resource instance', async () => {
122-
leakedResources.add(
123-
JSON.stringify({ name, plural: resource, namespace: namespaced ? testName : undefined }),
124-
);
125-
await yamlView.saveButton.click();
126-
127-
expect(crudView.errorMessage.isPresent()).toBe(false);
128-
});
129-
130-
it('displays detail view for new resource instance', async () => {
131-
await browser.wait(until.presenceOf(crudView.resourceTitle));
132-
expect(browser.getCurrentUrl()).toContain(`/${name}`);
133-
expect(crudView.resourceTitle.getText()).toEqual(name);
134-
});
135-
136-
it('search view displays created resource instance', async () => {
137-
await browser.get(
138-
`${appHost}/search/${
139-
namespaced ? `ns/${testName}` : 'all-namespaces'
140-
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
141-
);
142-
await crudView.resourceRowsPresent();
143-
await crudView
144-
.rowForName(name)
145-
.element(by.linkText(name))
146-
.click();
147-
await browser.wait(until.urlContains(`/${name}`));
148-
expect(crudView.resourceTitle.getText()).toEqual(name);
149-
});
150-
151-
it('edit the resource instance', async () => {
152-
if (kind !== 'ServiceAccount') {
153-
await browser.get(
154-
`${appHost}/search/${
155-
namespaced ? `ns/${testName}` : 'all-namespaces'
156-
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
157-
);
158-
await crudView.resourceRowsPresent();
159-
await crudView.editRow(kind)(name);
160-
}
161-
});
162-
163-
it('deletes the resource instance', async () => {
164-
await browser.get(
165-
`${appHost}${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}`,
166-
);
167-
await crudView.resourceRowsPresent();
168-
// Filter by resource name to make sure the resource is on the first page of results.
169-
// Otherwise the tests fail since we do virtual scrolling and the element isn't found.
170-
await crudView.filterForName(name);
171-
await crudView.deleteRow(kind)(name);
172-
leakedResources.delete(
173-
JSON.stringify({ name, plural: resource, namespace: namespaced ? testName : undefined }),
174-
);
175-
});
176-
});
177-
});
178-
17944
describe('Role Bindings', () => {
18045
const bindingName = `${testName}-cluster-admin`;
18146
const roleName = 'cluster-admin';
@@ -228,41 +93,6 @@ describe('Kubernetes resource CRUD operations', () => {
22893
});
22994
});
23095

231-
describe('Namespace', () => {
232-
const name = `${testName}-ns`;
233-
234-
it('displays `Namespace` list view', async () => {
235-
await browser.get(`${appHost}/k8s/cluster/namespaces`);
236-
await crudView.isLoaded();
237-
238-
expect(crudView.rowForName(name).isPresent()).toBe(false);
239-
});
240-
241-
it('creates the namespace', async () => {
242-
await crudView.createYAMLButton.click();
243-
await browser.wait(until.presenceOf($('.modal-body__field')));
244-
await $$('.modal-body__field')
245-
.get(0)
246-
.$('input')
247-
.sendKeys(name);
248-
leakedResources.add(JSON.stringify({ name, plural: 'namespaces' }));
249-
await $('#confirm-action').click();
250-
await browser.wait(until.invisibilityOf($('.modal-content')), K8S_CREATION_TIMEOUT);
251-
252-
expect(browser.getCurrentUrl()).toContain(`/k8s/cluster/namespaces/${testName}-ns`);
253-
});
254-
255-
it('deletes the namespace', async () => {
256-
await browser.get(`${appHost}/k8s/cluster/namespaces`);
257-
// Filter by resource name to make sure the resource is on the first page of results.
258-
// Otherwise the tests fail since we do virtual scrolling and the element isn't found.
259-
await crudView.filterForName(name);
260-
await crudView.resourceRowsPresent();
261-
await crudView.deleteRow('Namespace')(name);
262-
leakedResources.delete(JSON.stringify({ name, plural: 'namespaces' }));
263-
});
264-
});
265-
26696
describe('CustomResourceDefinitions', () => {
26797
const plural = `crd${testName}`;
26898
const group = 'test.example.com';

0 commit comments

Comments
 (0)