Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve E2E setup #803

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ yarn-error.log*
.vscode

# Cypress
src/testing/e2e/cypress/screenshots
src/testing/e2e/screenshots
9 changes: 6 additions & 3 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { defineConfig } from 'cypress';
import webpackPreprocessor from '@cypress/webpack-preprocessor';

import mainWebpackConfig from './webpack.config';
import HtmlWebpackPlugin from 'html-webpack-plugin';

export const webpackConfiguration = async (f: Cypress.FileObject) => {
const opts = { webpackOptions: { ...mainWebpackConfig, plugins: [] } };
// HtmlWebpackPlugin is clashing with cypress own html file process
const plugins = mainWebpackConfig.plugins.filter(f => !(f instanceof HtmlWebpackPlugin));
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

const opts = { webpackOptions: { ...mainWebpackConfig, plugins } };

return await webpackPreprocessor(opts as any)(f);
};

export default defineConfig({
projectId: '6to9oy',
e2e: {
specPattern: `src/testing/e2e/cypress/scenarios/**/*.cy.{js,jsx,ts,tsx}`,
specPattern: `src/testing/e2e/scenarios/**/*.cy.{js,jsx,ts,tsx}`,
baseUrl: process.env.BASE_URL ?? 'http://127.0.0.1:8080',
supportFile: 'src/testing/e2e/cypress/support/index.ts',
supportFile: 'src/testing/e2e/support/index.ts',
chromeWebSecurity: false,
env: {
username: 'admin@example.com',
Expand Down
9 changes: 5 additions & 4 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import type { Config } from 'jest';
import { pathsToModuleNameMapper } from 'ts-jest';
import tsconfig from './tsconfig.json';

const config: Config = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testMatch: [
'<rootDir>/src/**/?(*.)(spec|test).(j|t)s?(x)',
'<rootDir>/src/**/*.(spec|test).(j|t)s?(x)',
'<rootDir>/src/**/__tests__/**/*.(j|t)s?(x)',
],
roots: ['<rootDir>/src'],
moduleNameMapper: {
'\\.(png|jpg|gif|ttf|woff|woff2)$': '<rootDir>/scripts/assets-transformer.js',
'\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy',
'\\.svg$': '<rootDir>/scripts/svg-mock.js',
'^~/(.*)$': '<rootDir>/src/$1',
'^~backend/(.*)$': '<rootDir>/backend/$1',
'^~e2e/(.*)$': `<rootDir>/src/testing/e2e/$1`,
...pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { prefix: '<rootDir>/src' }),
},
modulePaths: [tsconfig.compilerOptions.baseUrl],
transform: {
'^.+\\.tsx?$': [
'ts-jest',
Expand Down
37 changes: 18 additions & 19 deletions src/components/AccessPoint/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React, { useLayoutEffect, useRef } from 'react';
import * as mobx from 'mobx';
import { observer } from 'mobx-react';

import * as e2e from '~e2e/client';

import { XY } from '~/domain/geometry';
import * as helpers from '~/domain/helpers';
import { IPProtocol, L7Kind, Verdict } from '~/domain/hubble';
import * as l7helpers from '~/domain/helpers/l7';

import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export interface Props {
port: number;
Expand All @@ -18,6 +16,13 @@ export interface Props {
connectorRef?: React.MutableRefObject<HTMLDivElement | null>;
}

export enum E2E {
accessPointPortTestId = 'port',
accessPointL4ProtoTestId = 'proto-l4',
accessPointL7ProtoTestId = 'proto-l7',
accessPoint = 'access-point',
}

export const AccessPoint = observer(function AccessPoint(props: Props) {
const connectorRef = useRef<HTMLDivElement>(null);

Expand All @@ -30,19 +35,13 @@ export const AccessPoint = observer(function AccessPoint(props: Props) {
props.connectorRef.current = connectorRef.current;
}, [props.connectorRef]);

const e2eAttrs = mobx
.computed(() => {
return e2e.attributes.serviceMap.accessPoint(
props.port,
props.l4Protocol,
props.l7Protocol,
props.verdicts,
);
})
.get();

return (
<div className={css.accessPoint} {...e2eAttrs}>
<div
className={css.accessPoint}
{...getTestAttributes(
`${E2E.accessPoint}-${helpers.protocol.toString(props.l4Protocol)}-${props.port}`,
)}
>
<div className={css.icons}>
<div className={css.circle} ref={connectorRef}>
<img src="icons/misc/access-point.svg" />
Expand All @@ -56,20 +55,20 @@ export const AccessPoint = observer(function AccessPoint(props: Props) {
<div className={css.data}>
{showPort && (
<>
<div className={css.port} {...e2e.attributes.serviceMap.portSelector()}>
<div className={css.port} {...getTestAttributes(E2E.accessPointPortTestId)}>
{props.port}
</div>
<div className={css.dot} />
</>
)}
<div className={css.protocol} {...e2e.attributes.serviceMap.l4ProtoSelector()}>
<div className={css.protocol} {...getTestAttributes(E2E.accessPointL4ProtoTestId)}>
{IPProtocol[props.l4Protocol]}
</div>

{props.l7Protocol && showL7Protocol && (
<>
<div className={css.dot} />
<div className={css.protocol} {...e2e.attributes.serviceMap.l7ProtoSelector()}>
<div className={css.protocol} {...getTestAttributes(E2E.accessPointL7ProtoTestId)}>
{l7helpers.l7KindToString(props.l7Protocol)}
</div>
</>
Expand Down
13 changes: 11 additions & 2 deletions src/components/Card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import { observer } from 'mobx-react';

import { XYWH } from '~/domain/geometry';
import { sizes } from '~/ui';
import * as e2e from '~e2e/client';

import type { CardProps, DivRef, SVGGElementRef, CoordsFn } from './general';

import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export type { CardProps, DivRef, SVGGElementRef, CoordsFn };

export enum E2E {
cardRootTestId = 'card-div-root',
}

export const Card = observer(function Card<C>(props: CardProps<C>) {
const divRef = useRef<HTMLDivElement>(null);

Expand All @@ -38,7 +42,12 @@ export const Card = observer(function Card<C>(props: CardProps<C>) {
return (
<g transform={`translate(${viewX}, ${viewY})`} onClick={() => props.onClick?.(props.card)}>
<foreignObject width={viewW} height={viewH}>
<div className={classes} ref={divRef} style={styles} {...e2e.attributes.card.selector()}>
<div
className={classes}
ref={divRef}
style={styles}
{...getTestAttributes(E2E.cardRootTestId)}
>
{props.children}
</div>
</foreignObject>
Expand Down
15 changes: 13 additions & 2 deletions src/components/EndpointCardHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { ServiceCard } from '~/domain/service-map';

import { EndpointLogo } from './EndpointLogo';

import * as e2e from '~e2e/client';
import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export enum E2E {
cardHeaderTestSelector = 'card-header',
cardIdTestSelector = 'card-id',
}

export interface Props {
card: ServiceCard;
Expand All @@ -23,7 +28,13 @@ export const EndpointCardHeader = memo(function EndpointCardHeader(props: Props)
});

return (
<div className={css.wrapper} {...e2e.attributes.serviceMap.card(card)}>
<div
className={css.wrapper}
{...getTestAttributes({
[E2E.cardHeaderTestSelector]: card.caption,
[E2E.cardIdTestSelector]: card.id,
})}
>
<div className={css.headline} onClick={props.onHeadlineClick}>
<EndpointLogo card={card} />

Expand Down
12 changes: 8 additions & 4 deletions src/components/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import { useMapZoom } from './hooks/useMapZoom';
import { useMutationObserver } from '~/ui/hooks/useMutationObserver';
import { ArrowStrategy, PlacementStrategy } from '~/ui/layout';

import * as e2e from '~e2e/client';

import { sizes } from '~/ui/vars';
import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export enum E2E {
visibleCardsTestId = 'visible-cards',
arrowForegroundTestId = 'arrows-foreground',
}

export interface Props<C extends AbstractCard> {
placement: PlacementStrategy;
Expand Down Expand Up @@ -112,9 +116,9 @@ export const MapElements = observer(function MapElements<C extends AbstractCard>
<g
className="arrows-foreground"
ref={arrowsForegroundRef}
{...e2e.attributes.map.arrowsForegroundSelector()}
{...getTestAttributes(E2E.arrowForegroundTestId)}
/>
<g ref={cardsRef} className="visible-cards" {...e2e.attributes.card.visibleContainer()}>
<g ref={cardsRef} className="visible-cards" {...getTestAttributes(E2E.visibleCardsTestId)}>
{cards}
</g>

Expand Down
17 changes: 13 additions & 4 deletions src/components/ServiceMapApp/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import { observer } from 'mobx-react';

import { NamespaceDescriptor } from '~/domain/namespaces';

import { e2e } from '~e2e/client';

import css from './WelcomeScreen.scss';
import hubbleLogo from '~/assets/images/hubble-logo.png';
import { getTestAttributes } from '~/testing/helpers';

export enum E2E {
namespaceAvailabilityTestSelector = 'availability',
namespaceAvailabilityTestValue = 'r',
namespaceNameTestSelector = 'name',
namespaceListTestId = 'ns-list',
}

export interface Props {
namespaces: NamespaceDescriptor[];
Expand All @@ -24,7 +30,7 @@ export const WelcomeScreen = observer(function WelcomeScreen(props: Props) {
<h1 className={css.title}>Welcome!</h1>
<p className={css.description}>To begin select one of the namespaces:</p>
{someNamespacesLoaded ? (
<ul className={css.namespacesList} {...e2e.attributes.ns.listSelector()}>
<ul className={css.namespacesList} {...getTestAttributes(E2E.namespaceListTestId)}>
{props.namespaces.map(ns => (
<NamespaceItem key={ns.namespace} namespace={ns} onClick={props.onNamespaceChange} />
))}
Expand Down Expand Up @@ -58,7 +64,10 @@ const NamespaceItem = observer(function NamespaceItem(props: NamespaceItemProps)
<a
href={`/${props.namespace.namespace}`}
onClick={onClick}
{...e2e.attributes.ns.entry(props.namespace.namespace)}
{...getTestAttributes({
[E2E.namespaceAvailabilityTestSelector]: E2E.namespaceAvailabilityTestValue,
[E2E.namespaceNameTestSelector]: props.namespace.namespace,
})}
>
{props.namespace.namespace}
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback } from 'react';
import { observer } from 'mobx-react';
import classnames from 'classnames';
import * as d3 from 'd3';
Expand All @@ -14,16 +14,22 @@ import { ArrowRendererProps } from '~/components/ArrowsRenderer';
import { Teleport } from '~/components/Teleport';

import { reactionRef } from '~/ui/react/refs';
import { e2e } from '~/testing/e2e/client';

import * as helpers from './helpers';
import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export type Props = Omit<ArrowRendererProps, 'arrow'> & {
arrows: Map<string, AccessPointArrow>;
connectorId: string;
};

export enum E2E {
duckFeetTestSelector = 'duck-feet-to-connector-id',
accessPointTestId = 'ap-lines',
innerLineTestSelector = 'inner-line',
}

export const ServiceMapArrowDuckFeet = observer(function ServiceMapArrowDuckFeet(props: Props) {
const connectorCapRef = reactionRef<SVGGElement | null>(null, e => {
renderEndingConnectorCap(e, props.connectorId, props.arrows);
Expand All @@ -32,8 +38,6 @@ export const ServiceMapArrowDuckFeet = observer(function ServiceMapArrowDuckFeet
renderInnerArrows(e, props.arrows);
});

const e2eAttrs = e2e.attributes.serviceMap;

const renderEndingConnectorCap = useCallback(
(target: SVGGElement | null, connectorId: string, arrows: Map<string, AccessPointArrow>) => {
const connectorPosition = MapUtils.pickFirst(arrows)?.start;
Expand Down Expand Up @@ -109,10 +113,21 @@ export const ServiceMapArrowDuckFeet = observer(function ServiceMapArrowDuckFeet
.enter()
.append('line')
.attr('class', 'inner')
.attr(
e2eAttrs.innerLineAttrName(),
e2e.attributes.nullOr(arr => arr.accessPointId),
)
.each(function (d) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self: any = this;
const e2eAttributes =
d.accessPointId &&
getTestAttributes({
[E2E.innerLineTestSelector]: d.accessPointId,
});

if (e2eAttributes) {
Object.keys(e2eAttributes).forEach(e2eAttr => {
d3.select(self).attr(e2eAttr, e2eAttributes[e2eAttr]);
});
}
})
.attr('stroke', helpers.innerArrows.strokeColor)
.attr('stroke-width', helpers.innerArrows.strokeWidth)
.attr('stroke-dasharray', helpers.innerArrows.strokeStyle)
Expand Down Expand Up @@ -166,11 +181,14 @@ export const ServiceMapArrowDuckFeet = observer(function ServiceMapArrowDuckFeet

return (
<Teleport to={props.arrowsForeground}>
<g className={classes} {...e2eAttrs.duckFeet(props.connectorId)}>
<g
className={classes}
{...getTestAttributes({ [E2E.duckFeetTestSelector]: props.connectorId })}
>
<g
className="arrows-to-access-points"
ref={innerArrowsRef}
{...e2eAttrs.linesToAccessPointsSelector()}
{...getTestAttributes(E2E.accessPointTestId)}
></g>
<g className="connector-cap" ref={connectorCapRef}></g>
</g>
Expand Down
13 changes: 11 additions & 2 deletions src/components/TopBar/NamespaceSelectorDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import { Button, MenuItem } from '@blueprintjs/core';

import { NamespaceDescriptor } from '~/domain/namespaces';
import { usePopover } from '~/ui/hooks/usePopover';
import * as e2e from '~e2e/client';

import css from './styles.scss';
import { getTestAttributes } from '~/testing/helpers';

export enum E2E {
namespaceAvailabilityTestSelector = 'availability',
namespaceAvailabilityTestValue = 'r',
namespaceNameTestSelector = 'name',
}

const renderItem: ItemRenderer<NamespaceDescriptor> = (ns, itemProps) => {
const { handleClick, modifiers } = itemProps;
Expand All @@ -20,7 +26,10 @@ const renderItem: ItemRenderer<NamespaceDescriptor> = (ns, itemProps) => {
key={ns.namespace}
onClick={handleClick}
text={ns.namespace}
{...e2e.attributes.ns.entry(ns.namespace)}
{...getTestAttributes({
[E2E.namespaceAvailabilityTestSelector]: E2E.namespaceAvailabilityTestValue,
[E2E.namespaceNameTestSelector]: ns.namespace,
})}
/>
);
};
Expand Down
Loading
Loading