Skip to content

Commit

Permalink
added i18n testing to cypress crud test suite
Browse files Browse the repository at this point in the history
  • Loading branch information
dtaylor113 committed Nov 17, 2020
1 parent f96e815 commit 3606f14
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 136 deletions.
39 changes: 39 additions & 0 deletions frontend/packages/integration-tests-cypress/support/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,52 @@
import { expect } from 'chai';
import { listPage } from '../views/list-page';

declare global {
namespace Cypress {
interface Chainable<Subject> {
isPseudoLocalized(): Chainable<Element>;
testI18n(kind: string, selectors?: string[], testIDs?: string[]): Chainable<Element>;
}
}
}

Cypress.Commands.add(
'testI18n',
(kind: string, selectors: string[] = [], testIDs: string[] = []) => {
cy.location().then((loc) => {
const params = new URLSearchParams(loc.search);
params.set('pseudolocalization', 'true');
params.set('lng', 'en');
const pseudoLocUrl = `${loc.pathname}?${params.toString()}`;

cy.log(`Testing I18n for ${kind}: ${pseudoLocUrl}`);

cy.visit(pseudoLocUrl);

// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000); // don't know what to wait for since could be list or detial page

// if PF toolbar, click to open 'search by' dropdown
cy.get('#content').then(($body) => {
if ($body.find(`#filter-toolbar`).length) {
listPage.filter.clickSearchByDropdown();
cy.get('.pf-c-dropdown__menu-item').isPseudoLocalized(); // 'search by' menu items
}

testIDs.forEach((testId) => cy.byTestID(testId).isPseudoLocalized());
selectors.forEach((selector) =>
cy.get(selector).each(($el) => {
const i18nNotTranslatedAttr = $el.attr('i18n-not-translated');
if (typeof i18nNotTranslatedAttr === typeof undefined) {
cy.wrap($el).isPseudoLocalized();
}
}),
);
});
});
},
);

Cypress.Commands.add(
'isPseudoLocalized',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,55 +24,72 @@ describe('Kubernetes resource CRUD operations', () => {
cy.logout();
});

const tableColumnHeaders =
'th > .pf-c-table__button > .pf-c-table__button-content > .pf-c-table__text';
const detailSectionHeadings = '[data-test-section-heading]';
const detailLabels = 'dt';
const detailHorizNavTabs = '.co-m-horizontal-nav__menu-item';

const k8sObjs = OrderedMap<
string,
{ kind: string; namespaced?: boolean; humanizeKind?: boolean }
{ kind: string; namespaced?: boolean; humanizeKind?: boolean; testI18n?: boolean }
>()
.set('pods', { kind: 'Pod' })
.set('services', { kind: 'Service' })
.set('serviceaccounts', { kind: 'ServiceAccount' })
.set('secrets', { kind: 'Secret' })
.set('serviceaccounts', { kind: 'ServiceAccount', testI18n: false })
.set('secrets', { kind: 'Secret', testI18n: false })
.set('configmaps', { kind: 'ConfigMap' })
.set('persistentvolumes', { kind: 'PersistentVolume', namespaced: false })
.set('storageclasses', { kind: 'StorageClass', namespaced: false })
.set('persistentvolumes', { kind: 'PersistentVolume', namespaced: false, testI18n: false })
.set('storageclasses', { kind: 'StorageClass', namespaced: false, testI18n: false })
.set('ingresses', { kind: 'Ingress' })
.set('cronjobs', { kind: 'CronJob' })
.set('jobs', { kind: 'Job' })
.set('daemonsets', { kind: 'DaemonSet' })
.set('deployments', { kind: 'Deployment' })
.set('replicasets', { kind: 'ReplicaSet' })
.set('replicationcontrollers', { kind: 'ReplicationController' })
.set('persistentvolumeclaims', { kind: 'PersistentVolumeClaim' })
.set('persistentvolumeclaims', { kind: 'PersistentVolumeClaim', testI18n: false })
.set('statefulsets', { kind: 'StatefulSet' })
.set('resourcequotas', { kind: 'ResourceQuota', humanizeKind: false })
.set('limitranges', { kind: 'LimitRange', humanizeKind: false })
.set('horizontalpodautoscalers', { kind: 'HorizontalPodAutoscaler' })
.set('networkpolicies', { kind: 'NetworkPolicy' })
.set('roles', { kind: 'Role' })
.set('roles', { kind: 'Role', testI18n: false })
.set('snapshot.storage.k8s.io~v1beta1~VolumeSnapshot', {
kind: 'snapshot.storage.k8s.io~v1beta1~VolumeSnapshot',
testI18n: false,
})
.set('snapshot.storage.k8s.io~v1beta1~VolumeSnapshotClass', {
kind: 'snapshot.storage.k8s.io~v1beta1~VolumeSnapshotClass',
namespaced: false,
testI18n: false,
})
.set('snapshot.storage.k8s.io~v1beta1~VolumeSnapshotContent', {
kind: 'snapshot.storage.k8s.io~v1beta1~VolumeSnapshotContent',
namespaced: false,
testI18n: false,
});
const openshiftObjs = OrderedMap<string, { kind: string; namespaced?: boolean }>()
const openshiftObjs = OrderedMap<
string,
{ kind: string; namespaced?: boolean; testI18n?: boolean }
>()
.set('deploymentconfigs', { kind: 'DeploymentConfig' })
.set('buildconfigs', { kind: 'BuildConfig' })
.set('imagestreams', { kind: 'ImageStream' })
.set('routes', { kind: 'Route' })
.set('user.openshift.io~v1~Group', { kind: 'user.openshift.io~v1~Group', namespaced: false });
const serviceCatalogObjs = OrderedMap<string, { kind: string; namespaced?: boolean }>().set(
'clusterservicebrokers',
{
kind: 'servicecatalog.k8s.io~v1beta1~ClusterServiceBroker',
.set('user.openshift.io~v1~Group', {
kind: 'user.openshift.io~v1~Group',
namespaced: false,
},
);
testI18n: false,
});
const serviceCatalogObjs = OrderedMap<
string,
{ kind: string; namespaced?: boolean; testI18n?: boolean }
>().set('clusterservicebrokers', {
kind: 'servicecatalog.k8s.io~v1beta1~ClusterServiceBroker',
namespaced: false,
testI18n: false,
});

let testObjs = Cypress.env('openshift') === true ? k8sObjs.merge(openshiftObjs) : k8sObjs;
testObjs = Cypress.env('servicecatalog') === true ? testObjs.merge(serviceCatalogObjs) : testObjs;
Expand All @@ -84,106 +101,125 @@ describe('Kubernetes resource CRUD operations', () => {
'snapshot.storage.k8s.io~v1beta1~VolumeSnapshot',
]);

testObjs.forEach(({ kind, namespaced = true, humanizeKind = true }, resource) => {
describe(kind, () => {
const name = `${testName}-${_.kebabCase(kind)}`;

it(`creates the resource instance`, () => {
cy.visit(
`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}?name=${testName}`,
);
if (kind === 'Secret') {
listPage.clickCreateYAMLdropdownButton();
} else {
listPage.clickCreateYAMLbutton();
}
if (resourcesWithCreationForm.has(kind)) {
cy.byTestID('yaml-link').click();
}
// sidebar needs to be fully loaded, else it sometimes overlays the Create button
cy.byTestID('resource-sidebar').should('exist');
yamlEditor.isLoaded();
cy.testA11y(`YAML Editor for ${kind}: ${name}`);
let newContent;
// get, update, and set yaml editor content.
yamlEditor.getEditorContent().then((content) => {
newContent = _.defaultsDeep(
{},
{ metadata: { name, labels: { [testLabel]: testName } } },
safeLoad(content),
// for debugging only, often a need to test a subset of kinds/objs
// ex: const testOnly = ['Pod', 'ResourceQuota'];
const testOnly = [];

testObjs.forEach(
({ kind, namespaced = true, humanizeKind = true, testI18n = true }, resource) => {
if (testOnly && testOnly.length > 0 && !testOnly.includes(kind)) {
return;
}
describe(kind, () => {
const name = `${testName}-${_.kebabCase(kind)}`;

it(`creates the resource instance`, () => {
cy.visit(
`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}?name=${testName}`,
);
yamlEditor.setEditorContent(safeDump(newContent, { sortKeys: true })).then(() => {
yamlEditor.clickSaveCreateButton();
cy.get(errorMessage).should('not.exist');
if (kind === 'Secret') {
listPage.clickCreateYAMLdropdownButton();
} else {
listPage.clickCreateYAMLbutton();
}
if (resourcesWithCreationForm.has(kind)) {
cy.byTestID('yaml-link').click();
}
// sidebar needs to be fully loaded, else it sometimes overlays the Create button
cy.byTestID('resource-sidebar').should('exist');
yamlEditor.isLoaded();
cy.testA11y(`YAML Editor for ${kind}: ${name}`);
let newContent;
// get, update, and set yaml editor content.
yamlEditor.getEditorContent().then((content) => {
newContent = _.defaultsDeep(
{},
{ metadata: { name, labels: { [testLabel]: testName } } },
safeLoad(content),
);
yamlEditor.setEditorContent(safeDump(newContent, { sortKeys: true })).then(() => {
yamlEditor.clickSaveCreateButton();
cy.get(errorMessage).should('not.exist');
});
});
});
});

it('displays detail view for newly created resource instance', () => {
cy.url().should('include', `/${name}`);
detailsPage.isLoaded();
detailsPage.titleShouldContain(name);
cy.testA11y(`Details page for ${kind}: ${name}`);
});
it('displays detail view for newly created resource instance', () => {
cy.url().should('include', `/${name}`);
detailsPage.isLoaded();
detailsPage.titleShouldContain(name);
cy.testA11y(`Details page for ${kind}: ${name}`);
if (testI18n) {
cy.testI18n(
kind,
[detailHorizNavTabs, detailSectionHeadings, detailLabels],
['timestamp'],
);
}
});

it(`displays a list view for the resource`, () => {
cy.visit(
`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}?name=${testName}`,
);
if (namespaced) {
// should have a namespace dropdown for namespaced objects');
listPage.projectDropdownShouldExist();
listPage.projectDropdownShouldContain(testName);
} else {
// should not have a namespace dropdown for non-namespaced objects');
listPage.projectDropdownShouldNotExist();
}
listPage.rows.shouldBeLoaded();
cy.testA11y(`List page for ${kind}: ${name}`);
});
it(`displays a list view for the resource`, () => {
cy.visit(
`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}?name=${testName}`,
);
if (namespaced) {
// should have a namespace dropdown for namespaced objects');
listPage.projectDropdownShouldExist();
listPage.projectDropdownShouldContain(testName);
} else {
// should not have a namespace dropdown for non-namespaced objects');
listPage.projectDropdownShouldNotExist();
}
listPage.rows.shouldBeLoaded();
cy.testA11y(`List page for ${kind}: ${name}`);
if (testI18n) {
cy.testI18n(kind, [tableColumnHeaders], kind !== 'Secret' ? ['yaml-create'] : []);
}
});

it('search view displays created resource instance', () => {
cy.visit(
`/search/${
namespaced ? `ns/${testName}` : 'all-namespaces'
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
);

// filter should have 3 chip groups: resource, label, and name
listPage.filter.numberOfActiveFiltersShouldBe(3);
listPage.rows.shouldExist(name);
cy.testA11y(`Search page for ${kind}: ${name}`);

// link to to details page
listPage.rows.clickRowByName(name);
cy.url().should('include', `/${name}`);
detailsPage.titleShouldContain(name);
});
it('search view displays created resource instance', () => {
cy.visit(
`/search/${
namespaced ? `ns/${testName}` : 'all-namespaces'
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
);

it('edits the resource instance', () => {
cy.visit(
`/search/${
namespaced ? `ns/${testName}` : 'all-namespaces'
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
);
listPage.rows.clickKebabAction(name, editKind(kind, humanizeKind));
if (kind !== 'Secret') {
yamlEditor.isLoaded();
yamlEditor.clickReloadButton();
}
yamlEditor.clickSaveCreateButton();
});
// filter should have 3 chip groups: resource, label, and name
listPage.filter.numberOfActiveFiltersShouldBe(3);
listPage.rows.shouldExist(name);
cy.testA11y(`Search page for ${kind}: ${name}`);

// link to to details page
listPage.rows.clickRowByName(name);
cy.url().should('include', `/${name}`);
detailsPage.titleShouldContain(name);
});

it(`deletes the resource instance`, () => {
cy.visit(`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}`);
listPage.filter.byName(name);
listPage.rows.countShouldBe(1);
listPage.rows.clickKebabAction(name, deleteKind(kind, humanizeKind));
modal.shouldBeOpened();
modal.submit();
modal.shouldBeClosed();
cy.resourceShouldBeDeleted(testName, resource, name);
it('edits the resource instance', () => {
cy.visit(
`/search/${
namespaced ? `ns/${testName}` : 'all-namespaces'
}?kind=${kind}&q=${testLabel}%3d${testName}&name=${name}`,
);
listPage.rows.clickKebabAction(name, editKind(kind, humanizeKind));
if (kind !== 'Secret') {
yamlEditor.isLoaded();
yamlEditor.clickReloadButton();
}
yamlEditor.clickSaveCreateButton();
});

it(`deletes the resource instance`, () => {
cy.visit(`${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}`);
listPage.filter.byName(name);
listPage.rows.countShouldBe(1);
listPage.rows.clickKebabAction(name, deleteKind(kind, humanizeKind));
modal.shouldBeOpened();
modal.submit();
modal.shouldBeClosed();
cy.resourceShouldBeDeleted(testName, resource, name);
});
});
});
});
},
);
});
11 changes: 9 additions & 2 deletions frontend/packages/integration-tests-cypress/views/list-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export const listPage = {
numberOfActiveFiltersShouldBe: (numFilters: number) => {
cy.get("[class='pf-c-toolbar__item pf-m-chip-group']").should('have.length', numFilters);
},
clickSearchByDropdown: () => {
cy.get('.pf-c-toolbar__content-section').within(() => {
cy.byLegacyTestID('dropdown-button').click();
});
},
},
rows: {
shouldBeLoaded: () => {
Expand All @@ -38,8 +43,10 @@ export const listPage = {
clickKebabAction: (resourceName: string, actionName: string) => {
cy.get(`[data-test-rows="resource-row"]`)
.contains(resourceName)
.byLegacyTestID('kebab-button')
.click();
.parents('tr')
.within(() => {
cy.get('[data-test-id="kebab-button"]').click();
});
cy.byTestActionID(actionName).click();
},
hasLabel: (resourceName: string, label: string) => {
Expand Down
6 changes: 5 additions & 1 deletion frontend/public/components/configmap-and-secret-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export const ConfigMapData: React.FC<ConfigMapDataProps> = ({ data, label }) =>
.sort()
.forEach((k) => {
const value = data[k];
dl.push(<dt key={`${k}-k`}>{k}</dt>);
dl.push(
<dt i18n-not-translated="" key={`${k}-k`}>
{k}
</dt>,
);
dl.push(
<dd key={`${k}-v`}>
<CopyToClipboard value={value} />
Expand Down

0 comments on commit 3606f14

Please sign in to comment.