Skip to content

Commit

Permalink
test: det framework supports "nth" component [testeng-1] (#9540)
Browse files Browse the repository at this point in the history
  • Loading branch information
JComins000 authored Jun 18, 2024
1 parent 7568129 commit ea929fc
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 24 deletions.
80 changes: 70 additions & 10 deletions webui/react/src/e2e/models/BaseComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface ComponentBasics extends ModelBasics {
get root(): BasePage;
}

function implementsComponentBasics(obj: object): obj is ComponentBasics {
return '_parent' in obj && 'root' in obj;
}

interface ComponentArgBasics {
parent: CanBeParent;
}
Expand Down Expand Up @@ -62,7 +66,7 @@ export class BaseComponent implements ComponentBasics {
*/
get pwLocator(): Locator {
// Treat the locator as a readonly, but only after we've created it
if (this._locator === undefined) {
if (!Object.prototype.hasOwnProperty.call(this, '_locator') || this._locator === undefined) {
this._locator = this._parent.pwLocator.locator(this.selector);
}
return this._locator;
Expand All @@ -72,17 +76,73 @@ export class BaseComponent implements ComponentBasics {
* Returns the root of the component tree
*/
get root(): BasePage {
let root: CanBeParent = this._parent;
while (!(root instanceof BasePage)) {
root = root._parent;
if (this._parent instanceof BasePage) {
return this._parent;
} else {
return this._parent.root;
}
return root;
}

/**
* Returns the nth component which matches the selector
*/
nth(n: number): this {
/**
* @param {T1} containerComponent the component to search properties for references to oldParent
* @param {T2} oldParent the old parent to replace
* @param {T2} newParent the new parent to replace with
*
* iterate through every property in the containerComponent prototype, if a
* property is a component and it's parent is oldParent, replace it with a
* new component with the same properties but a new parent.
*/
const replaceParent = <T1 extends ComponentBasics, T2 extends ComponentBasics>(
containerComponent: T1,
oldParent: T2,
newParent: T2,
): void => {
// this isn't a deep search, but i don't expect components to be instanciated
// with a parent set to "this.parent". it's always set to "this" or another
// component with "this" as a parent.
for (const key in containerComponent) {
// special case for _parent so we don't recurse backwards
if (key === '_parent') continue;
const childComponent = containerComponent[key];
if (childComponent instanceof Object && implementsComponentBasics(childComponent)) {
// make a copy of the component and set it's parent to resetComponent
if (childComponent._parent === oldParent) {
// create a new object with the same properties as the childComponent
const newChildComponent = Object.create(childComponent);
// update the parent of the new object
newChildComponent._parent = newParent;
// update the property in containerComponent
containerComponent[key] = newChildComponent;
// update any properties in containerComponent which have a parent of childComponent
replaceParent(containerComponent, childComponent, newChildComponent);
// update any properties in 'newChildComponent' which has a parent of childComponent
replaceParent(newChildComponent, childComponent, newChildComponent);
}
}
}
};

const nthObj = Object.create(this, {
pwLocator: {
configurable: true,
enumerable: true,
get: () => {
return this.pwLocator.nth(n);
},
},
});
replaceParent(nthObj, this, nthObj);
return nthObj;
}
}

/**
* BaseReactFragment will preserve the parent locator heirachy while also
* providing a way to group elements, just like the React Fragments they model.
* providing a way to group components, just like the React Fragments they model.
*/
export class BaseReactFragment implements ComponentBasics {
readonly _parent: CanBeParent;
Expand All @@ -108,11 +168,11 @@ export class BaseReactFragment implements ComponentBasics {
* Returns the root of the component tree
*/
get root(): BasePage {
let root: CanBeParent = this._parent;
while (!(root instanceof BasePage)) {
root = root._parent;
if (this._parent instanceof BasePage) {
return this._parent;
} else {
return this._parent.root;
}
return root;
}
}

Expand Down
1 change: 0 additions & 1 deletion webui/react/src/e2e/models/BasePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export abstract class BasePage implements ModelBasics {

/**
* 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
Expand Down
22 changes: 9 additions & 13 deletions webui/react/src/e2e/tests/experimentList.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,7 @@ test.describe('Experiement List', () => {
await tableFilter.open();
await scenario();
// [ET-284] - Sometimes, closing the popover too quickly causes the filter to not apply.
// await waitTableStable();
await projectDetailsPage._page.waitForTimeout(1_000);
await waitTableStable();
await expect.poll(async () => await getExpNum()).toBe(expectedValue);
await tableFilter.close();
});
Expand Down Expand Up @@ -207,31 +206,28 @@ test.describe('Experiement List', () => {
await filterScenario(
'Filter OR',
async () => {
// This looks a little screwy with nth(1) in some places. Everything here is referring to the second filterfield row.
// [INFENG-715]
await tableFilter.filterForm.addCondition.pwLocator.click();

const conjunction =
tableFilter.filterForm.filter.filterFields.conjunctionContainer.conjunctionSelect;
const secondFilterField = tableFilter.filterForm.filter.filterFields.nth(1);
const conjunction = secondFilterField.conjunctionContainer.conjunctionSelect;
await conjunction.pwLocator.click();
await conjunction._menu.pwLocator.waitFor();
await conjunction.menuItem('or').pwLocator.click();
await conjunction._menu.pwLocator.waitFor({ state: 'hidden' });

const columnName = tableFilter.filterForm.filter.filterFields.columnName;
await columnName.pwLocator.nth(1).click();
const columnName = secondFilterField.columnName;
await columnName.pwLocator.click();
await columnName._menu.pwLocator.waitFor();
await columnName.menuItem('ID').pwLocator.click();
await columnName._menu.pwLocator.waitFor({ state: 'hidden' });

const operator = tableFilter.filterForm.filter.filterFields.operator;
await expect(operator.pwLocator.nth(1)).toHaveText('=');
await operator.pwLocator.nth(1).click();
const operator = secondFilterField.operator;
await expect(operator.pwLocator).toHaveText('=');
await operator.pwLocator.click();
await operator._menu.pwLocator.waitFor();
await operator.menuItem('=').pwLocator.click();
await operator._menu.pwLocator.waitFor({ state: 'hidden' });

await tableFilter.filterForm.filter.filterFields.valueNumber.pwLocator.nth(1).fill('1');
await secondFilterField.valueNumber.pwLocator.fill('1');
},
totalExperiments,
);
Expand Down

0 comments on commit ea929fc

Please sign in to comment.