Skip to content

Commit

Permalink
test: experiment list page models and sample test [INFENG-451] (#9139)
Browse files Browse the repository at this point in the history
  • Loading branch information
JComins000 authored Apr 15, 2024
1 parent fd45ed8 commit 2f874b9
Show file tree
Hide file tree
Showing 32 changed files with 634 additions and 100 deletions.
2 changes: 1 addition & 1 deletion webui/react/src/components/TableActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ const TableActionBar: React.FC<Props> = ({
const handleAction = useCallback((key: string) => handleBatchAction(key), [handleBatchAction]);

return (
<div className={css.base}>
<div className={css.base} data-test-component="tableActionBar">
<Row>
<Column>
<Row>
Expand Down
6 changes: 3 additions & 3 deletions webui/react/src/e2e/fixtures/dev.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, Page } from '@playwright/test';

import { BaseComponent, parentTypes } from 'e2e/models/BaseComponent';
import { BaseComponent, CanBeParent } from 'e2e/models/BaseComponent';
import { BasePage } from 'e2e/models/BasePage';

export class DevFixture {
Expand All @@ -23,8 +23,8 @@ export class DevFixture {
* @param {BaseComponent} component - The component to debug
*/
debugComponentVisible(component: BaseComponent): void {
const componentTree: parentTypes[] = [];
let root: parentTypes = component;
const componentTree: CanBeParent[] = [];
let root: CanBeParent = component;
while (!(root instanceof BasePage)) {
componentTree.unshift(root);
root = root._parent;
Expand Down
40 changes: 22 additions & 18 deletions webui/react/src/e2e/models/BaseComponent.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { type Locator } from '@playwright/test';

import { BasePage } from './BasePage';
import { BasePage, ModelBasics } from './BasePage';

// BasePage is the root of any tree, use `instanceof BasePage` when climbing.
export type parentTypes = BasePage | BaseComponent | BaseReactFragment;
export type CanBeParent = ComponentBasics | BasePage;

interface ComponentBasics {
parent: parentTypes;
export interface ComponentBasics extends ModelBasics {
_parent: CanBeParent;
get root(): BasePage;
}

interface NamedComponentWithDefaultSelector extends ComponentBasics {
interface ComponentArgBasics {
parent: CanBeParent;
}

interface NamedComponentWithDefaultSelector extends ComponentArgBasics {
attachment?: never;
sleector?: never;
}
interface NamedComponentWithAttachment extends ComponentBasics {
interface NamedComponentWithAttachment extends ComponentArgBasics {
attachment: string;
sleector?: never;
}
export interface BaseComponentArgs extends ComponentBasics {
export interface BaseComponentArgs extends ComponentArgBasics {
attachment?: never;
selector: string;
}
Expand All @@ -31,12 +35,12 @@ export type NamedComponentArgs =
* Returns the representation of a Component.
* This constructor is a base class for any component in src/components/.
* @param {object} obj
* @param {parentTypes} obj.parent - The parent used to locate this BaseComponent
* @param {CanBeParent} obj.parent - The parent used to locate this BaseComponent
* @param {string} obj.selector - Used as a selector uesd to locate this object
*/
export class BaseComponent {
export class BaseComponent implements ModelBasics {
protected _selector: string;
readonly _parent: parentTypes;
readonly _parent: CanBeParent;
protected _locator: Locator | undefined;

constructor({ parent, selector }: BaseComponentArgs) {
Expand All @@ -63,7 +67,7 @@ export class BaseComponent {
* Returns the root of the component tree
*/
get root(): BasePage {
let root: parentTypes = this._parent;
let root: CanBeParent = this._parent;
while (!(root instanceof BasePage)) {
root = root._parent;
}
Expand All @@ -76,12 +80,12 @@ export class BaseComponent {
* React Fragment Components are special in that they group elements, but not under a dir.
* Fragments cannot have selectors
* @param {object} obj
* @param {parentTypes} obj.parent - The parent used to locate this BaseComponent
* @param {CanBeParent} obj.parent - The parent used to locate this BaseComponent
*/
export class BaseReactFragment {
readonly _parent: parentTypes;
export class BaseReactFragment implements ModelBasics {
readonly _parent: CanBeParent;

constructor({ parent }: ComponentBasics) {
constructor({ parent }: ComponentArgBasics) {
this._parent = parent;
}

Expand All @@ -97,7 +101,7 @@ export class BaseReactFragment {
* Returns the root of the component tree
*/
get root(): BasePage {
let root: parentTypes = this._parent;
let root: CanBeParent = this._parent;
while (!(root instanceof BasePage)) {
root = root._parent;
}
Expand All @@ -108,7 +112,7 @@ export class BaseReactFragment {
/**
* Returns a representation of a named component. These components need a defaultSelector.
* @param {object} obj
* @param {parentTypes} obj.parent - The parent used to locate this NamedComponent
* @param {CanBeParent} obj.parent - The parent used to locate this NamedComponent
* @param {string} obj.selector - Used as a selector uesd to locate this object
*/
export abstract class NamedComponent extends BaseComponent {
Expand Down
32 changes: 22 additions & 10 deletions webui/react/src/e2e/models/BasePage.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Locator, type Page } from '@playwright/test';
import { expect, Locator, type Page } from '@playwright/test';

import { Navigation } from 'e2e/models/components/Navigation';

export interface ModelBasics {
get pwLocator(): Locator;
}

/**
* Returns the representation of a Page.
* This constructor is a base class for any component in src/pages/.
* @param {Page} page - The '@playwright/test' Page being used by a test
*/
export abstract class BasePage {
export abstract class BasePage implements ModelBasics {
readonly _page: Page;
readonly nav: Navigation = new Navigation({ parent: this });
abstract readonly url: string;
abstract readonly title: string;
abstract readonly url: string | RegExp;
abstract readonly title: string | RegExp;

constructor(page: Page) {
this._page = page;
Expand All @@ -25,14 +29,22 @@ export abstract class BasePage {
}

/**
* Returns this so we can chain.
* ie. await expect(thePage.goto().theElement.loc()).toBeVisible()
* @param {Page} [waitForURL] - Whether for the URL to change
* Returns this so we can chain. Visits the page.
* ie. await expect(thePage.goto().theElement.pwLocator()).toBeVisible()
* @param {{}} [args] - obj
* @param {string} args.url - A URL to visit. It can be different from the URL to verify
* @param {boolean} [args.verify] - Whether for the URL to change
*/
async goto(waitForURL: boolean = true): Promise<BasePage> {
await this._page.goto(this.url);
if (waitForURL) {
async goto(
{ url, verify = true }: { url: string | RegExp; verify?: boolean } = this,
): Promise<BasePage> {
if (url instanceof RegExp) {
throw new Error(`${typeof this}.url is a regular expression. Please provide a url to visit.`);
}
await this._page.goto(url);
if (verify) {
await this._page.waitForURL(this.url);
await expect(this._page).toHaveTitle(this.title);
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Modal } from 'e2e/models/hew/Modal';
* Returns a representation of the AddUsersToGroupsModal component.
* This constructor represents the contents in src/components/AddUsersToGroupsModal.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this AddUsersToGroupsModal
* @param {CanBeParent} obj.parent - The parent used to locate this AddUsersToGroupsModal
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class AddUsersToGroupsModal extends Modal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Modal } from 'e2e/models/hew/Modal';
* Returns a representation of the ChangeUserStatusModal component.
* This constructor represents the contents in src/components/ChangeUserStatusModal.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this ChangeUserStatusModal
* @param {CanBeParent} obj.parent - The parent used to locate this ChangeUserStatusModal
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class ChangeUserStatusModal extends Modal {
Expand Down
12 changes: 12 additions & 0 deletions webui/react/src/e2e/models/components/ComparisonView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SplitPane } from 'e2e/models/hew/SplitPane';

/**
* Returns a representation of the ComparisonView component.
* This constructor represents the contents in src/components/ComparisonView.tsx.
* @param {object} obj
* @param {CanBeParent} obj.parent - The parent used to locate this ComparisonView
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class ComparisonView extends SplitPane {
// TODO everything in here
}
2 changes: 1 addition & 1 deletion webui/react/src/e2e/models/components/CreateUserModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Modal } from 'e2e/models/hew/Modal';
* Returns a representation of the CreateUserModal component.
* This constructor represents the contents in src/components/CreateUserModal.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this CreateUserModal
* @param {CanBeParent} obj.parent - The parent used to locate this CreateUserModal
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class CreateUserModal extends Modal {
Expand Down
2 changes: 1 addition & 1 deletion webui/react/src/e2e/models/components/DeterminedAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ErrorComponent } from 'e2e/models/utils/error';
* Returns a representation of the DeterminedAuth component.
* This constructor represents the contents in src/components/DeterminedAuth.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this DeterminedAuth
* @param {CanBeParent} obj.parent - The parent used to locate this DeterminedAuth
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/

Expand Down
12 changes: 12 additions & 0 deletions webui/react/src/e2e/models/components/DynamicTabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BaseReactFragment } from 'e2e/models/BaseComponent';
import { Pivot } from 'e2e/models/hew/Pivot';

/**
* Returns a representation of the DynamicTabs component.
* This constructor represents the contents in src/components/DynamicTabs.tsx.
* @param {object} obj
* @param {CanBeParent} obj.parent - The parent used to locate this DynamicTabs
*/
export class DynamicTabs extends BaseReactFragment {
readonly pivot = new Pivot({ parent: this });
}
13 changes: 13 additions & 0 deletions webui/react/src/e2e/models/components/ExperimentActionDropdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Dropdown } from 'e2e/models/hew/Dropdown';

/**
* Returns the representation of the ActionDropdown menu defined by the User Admin page.
* This constructor represents the InteractiveTable in src/components/ExperimentActionDropdown.tsx.
* @param {object} obj
* @param {CanBeParent} obj.parent - The parent used to locate this ExperimentActionDropdown
* @param {string} obj.selector - Used as a selector uesd to locate this object
*/
export class ExperimentActionDropdown extends Dropdown {
// TODO where is this thing? <Button icon={<Icon name="overflow-vertical" size="small" title="Action menu" />} />
// TODO I'm assuming it's new tab, new window, copy value, etc
}
39 changes: 39 additions & 0 deletions webui/react/src/e2e/models/components/F_ExperiementList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { BaseReactFragment } from 'e2e/models/BaseComponent';
import { ComparisonView } from 'e2e/models/components/ComparisonView';
import { ExperimentActionDropdown } from 'e2e/models/components/ExperimentActionDropdown';
import { TableActionBar } from 'e2e/models/components/TableActionBar';
import { DataGrid, HeadRow, Row, RowArgs } from 'e2e/models/hew/DataGrid';
import { Pagination } from 'e2e/models/hew/Pagination';

/**
* Returns a representation of the F_ExperiementList component.
* This constructor represents the contents in src/components/F_ExperiementList.tsx.
* @param {object} obj
* @param {CanBeParent} obj.parent - The parent used to locate this F_ExperiementList
*/
export class F_ExperiementList extends BaseReactFragment {
readonly tableActionBar: TableActionBar = new TableActionBar({ parent: this });
// TODO no experiments
// TODO no filtered experiments
// TODO error
readonly comparisonView: ComparisonView = new ComparisonView({ parent: this });
readonly dataGrid: DataGrid<ExperimentRow, ExperimentHeadRow> = new DataGrid({
headRowType: ExperimentHeadRow,
parent: this.comparisonView.initial,
rowType: ExperimentRow,
});
// There is no button which activates this dropdown. To display it, right-click the grid
readonly experimentActionDropdown: ExperimentActionDropdown = new ExperimentActionDropdown({
parent: this.root,
selector: '',
});
readonly pagination: Pagination = new Pagination({ parent: this });
}

class ExperimentHeadRow extends HeadRow {}
class ExperimentRow extends Row<ExperimentRow, ExperimentHeadRow> {
constructor(args: RowArgs<ExperimentRow, ExperimentHeadRow>) {
super(args);
this.columnPositions.set('ID', 50);
}
}
2 changes: 1 addition & 1 deletion webui/react/src/e2e/models/components/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NavigationSideBar } from 'e2e/models/components/NavigationSideBar';
* Returns a representation of the Navigation component.
* This constructor represents the contents in src/components/Navigation.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this Navigation
* @param {CanBeParent} obj.parent - The parent used to locate this Navigation
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class Navigation extends NamedComponent {
Expand Down
3 changes: 1 addition & 2 deletions webui/react/src/e2e/models/components/NavigationSideBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { Dropdown } from 'e2e/models/hew/Dropdown';
* Returns a representation of the NavigationSideBar component.
* This constructor represents the contents in src/components/NavigationSideBar.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this NavigationSideBar
* @param {string} [obj.selector] - Used instead of `defaultSelector`
* @param {CanBeParent} obj.parent - The parent used to locate this NavigationSideBar
*/
export class NavigationSideBar extends BaseReactFragment {
readonly #nav: BaseComponent = new BaseComponent({
Expand Down
3 changes: 1 addition & 2 deletions webui/react/src/e2e/models/components/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { BaseReactFragment } from 'e2e/models/BaseComponent';
* Returns a representation of the Page component.
* This constructor represents the contents in src/components/Page.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this Page
* @param {string} [obj.selector] - Used instead of `defaultSelector`
* @param {CanBeParent} obj.parent - The parent used to locate this Page
*/
export class PageComponent extends BaseReactFragment {
// TODO define things like Spinners that are unique to Pagecomponent
Expand Down
2 changes: 1 addition & 1 deletion webui/react/src/e2e/models/components/SetUserRolesModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Modal } from 'e2e/models/hew/Modal';
* Returns a representation of the CreateUserModal component.
* This constructor represents the contents in src/components/CreateUserModal.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this CreateUserModal
* @param {CanBeParent} obj.parent - The parent used to locate this CreateUserModal
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class SetUserRolesModal extends Modal {
Expand Down
2 changes: 1 addition & 1 deletion webui/react/src/e2e/models/components/SkeletonSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BaseComponent, NamedComponent } from 'e2e/models/BaseComponent';
* Returns a representation of the SkeletonSection component.
* This constructor represents the contents in src/components/SkeletonSection.tsx.
* @param {object} obj
* @param {implementsGetLocator} obj.parent - The parent used to locate this SkeletonSection
* @param {CanBeParent} obj.parent - The parent used to locate this SkeletonSection
* @param {string} [obj.selector] - Used instead of `defaultSelector`
*/
export class SkeletonSection extends NamedComponent {
Expand Down
Loading

0 comments on commit 2f874b9

Please sign in to comment.