-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FI-494: add selectors with custom methods
- Loading branch information
Showing
20 changed files
with
478 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
autotests/tests/e2edReportExample/customSelectorMethods.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import {it} from 'autotests'; | ||
import {E2edReportExample} from 'autotests/pageObjects/pages'; | ||
import {expect} from 'e2ed'; | ||
import {navigateToPage} from 'e2ed/actions'; | ||
|
||
it('custom selector methods', {meta: {testId: '6'}, testIdleTimeout: 35_000}, async () => { | ||
const reportPage = await navigateToPage(E2edReportExample); | ||
|
||
await expect(reportPage.navigationRetries.exists, 'navigation retries exists').ok(); | ||
|
||
await expect(reportPage.navigationRetriesButton.exists, ' exists').ok(); | ||
|
||
await expect( | ||
reportPage.navigationRetriesButtonSelected.exists, | ||
'selected navigation retries button exists', | ||
).ok(); | ||
|
||
const buttonsCount = await reportPage.navigationRetriesButton.count; | ||
|
||
await expect( | ||
reportPage.navigationRetriesButtonSelected.getTestProp('retry'), | ||
'last navigation retries button selected', | ||
).eql(String(buttonsCount)); | ||
|
||
await expect( | ||
reportPage.navigationRetriesButtonSelected.hasTestProp('disabled'), | ||
'selected navigation retries button has "disabled" test prop', | ||
).ok(); | ||
|
||
await expect( | ||
reportPage.navigationRetriesButtonSelected.getDescription(), | ||
'selector has apropriate description', | ||
).eql(''); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
createSelector, | ||
createSelectorByCss, | ||
htmlElementSelector, | ||
locatorIdSelector, | ||
} from 'e2ed/selectors'; | ||
|
||
// @ts-expect-error: wrong number of arguments | ||
htmlElementSelector.findByTestId(); | ||
// @ts-expect-error: wrong type of arguments | ||
htmlElementSelector.findByTestId(0); | ||
// ok | ||
htmlElementSelector.findByTestId('id'); | ||
// ok | ||
htmlElementSelector.findByTestId('id').findByTestId('id2'); | ||
// ok | ||
htmlElementSelector.findByTestId('id').find('.test-children'); | ||
// ok | ||
htmlElementSelector.find('body').findByTestId('id'); | ||
|
||
// ok | ||
createSelector('id').findByTestId('id').find('body').findByTestId('id'); | ||
// ok | ||
createSelectorByCss('id').findByTestId('id').find('body').findByTestId('id'); | ||
// ok | ||
locatorIdSelector('id').findByTestId('id').find('body').findByTestId('id'); | ||
|
||
// ok | ||
htmlElementSelector.filterByTestId('id'); | ||
// ok | ||
htmlElementSelector.parentByTestId('id'); | ||
// ok | ||
htmlElementSelector.childByTestId('id'); | ||
// ok | ||
htmlElementSelector.siblingByTestId('id'); | ||
// ok | ||
htmlElementSelector.nextSiblingByTestId('id'); | ||
// ok | ||
htmlElementSelector.prevSiblingByTestId('id'); | ||
|
||
// ok | ||
htmlElementSelector.filterByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.findByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.parentByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.childByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.siblingByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.nextSiblingByTestProp('prop', 'value'); | ||
// ok | ||
htmlElementSelector.prevSiblingByTestProp('prop', 'value'); | ||
|
||
// ok | ||
void htmlElementSelector.getTestProp('prop'); | ||
// ok | ||
void htmlElementSelector.hasTestProp('prop'); | ||
|
||
// ok | ||
declare function testStringOrUndefined(val: string | undefined): void; | ||
testStringOrUndefined(htmlElementSelector.getDescription()); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import {Selector} from 'testcafe-without-typecheck'; | ||
|
||
import {DESCRIPTION_KEY} from '../constants/internal'; | ||
|
||
import type {Fn, RawSelector, SelectorCustomMethods, Values} from '../types/internal'; | ||
|
||
/** | ||
* Proxy handler for wrapping all selector properties. | ||
*/ | ||
const createGet = <CustomMethods extends SelectorCustomMethods = {}>( | ||
customMethods?: CustomMethods, | ||
): Required<ProxyHandler<RawSelector<CustomMethods>>>['get'] => { | ||
const get: Required<ProxyHandler<RawSelector<CustomMethods>>>['get'] = ( | ||
target, | ||
property, | ||
receiver, | ||
) => { | ||
const customMethod = | ||
customMethods && typeof property === 'string' ? customMethods[property] : undefined; | ||
|
||
let result = ( | ||
customMethod ? customMethod.bind(target) : Reflect.get(target, property, receiver) | ||
) as Values<RawSelector<CustomMethods>> & { | ||
[DESCRIPTION_KEY]?: string; | ||
}; | ||
|
||
if (typeof property === 'symbol') { | ||
return result; | ||
} | ||
|
||
if ((typeof result !== 'object' || result === null) && typeof result !== 'function') { | ||
return result; | ||
} | ||
|
||
if (typeof result === 'function') { | ||
const originalFunction = result as Fn; | ||
|
||
result = // eslint-disable-next-line no-restricted-syntax | ||
function selectorMethodWrapper(this: RawSelector, ...args: never[]) { | ||
const callResult = originalFunction.apply(this, args); | ||
|
||
if ( | ||
(typeof callResult !== 'object' || callResult === null) && | ||
typeof callResult !== 'function' | ||
) { | ||
return callResult; | ||
} | ||
|
||
const callLocator = `${result[DESCRIPTION_KEY]}(${args.join(', ')})`; | ||
|
||
(callResult as typeof result)[DESCRIPTION_KEY] = callLocator; | ||
|
||
// callResult is Selector | ||
if (Object.prototype.hasOwnProperty.call(callResult, 'getBoundingClientRectProperty')) { | ||
return new Proxy(callResult, {get}); | ||
} | ||
|
||
return callResult; | ||
} as typeof result; | ||
} | ||
|
||
const locator = target[DESCRIPTION_KEY] || ''; | ||
|
||
result[DESCRIPTION_KEY] = `${locator}.${property}`; | ||
|
||
return result; | ||
}; | ||
|
||
return get; | ||
}; | ||
|
||
export type CreateSelector<CustomMethods extends SelectorCustomMethods = {}> = ( | ||
...args: Parameters<typeof Selector> | ||
) => RawSelector<CustomMethods>; | ||
|
||
export const createSelectorCreator = <CustomMethods extends SelectorCustomMethods = {}>( | ||
customMethods?: CustomMethods, | ||
): CreateSelector<CustomMethods> => { | ||
const createSelector: CreateSelector<CustomMethods> = (...args) => { | ||
const locator = args[0]; | ||
const selector = Selector(...args) as RawSelector<CustomMethods>; | ||
|
||
if (typeof locator === 'string') { | ||
selector[DESCRIPTION_KEY] = locator; | ||
} | ||
|
||
return new Proxy(selector, {get: createGet(customMethods)}); | ||
}; | ||
|
||
return createSelector; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,15 @@ | ||
import {createSelector} from '../createSelector'; | ||
import type {RawSelector, SelectorCustomMethods} from '../types/internal'; | ||
|
||
import type {Selector} from '../types/internal'; | ||
import type {CreateSelector} from './createSelector'; | ||
|
||
/** | ||
* Creates selector of page elements by CSS selector. | ||
*/ | ||
export const createSelectorByCss = (cssSelectorString: string): Selector => | ||
createSelector(cssSelectorString); | ||
export const createSelectorByCssCreator = <CustomMethods extends SelectorCustomMethods = {}>( | ||
createSelector: CreateSelector<CustomMethods>, | ||
): typeof createSelectorByCss => { | ||
/** | ||
* Creates selector of page elements by CSS selector. | ||
*/ | ||
const createSelectorByCss = (cssSelectorString: string): RawSelector<CustomMethods> => | ||
createSelector(cssSelectorString); | ||
|
||
return createSelectorByCss; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import {createSelectorCreator} from './createSelector'; | ||
import {createSelectorByCssCreator} from './createSelectorByCss'; | ||
import {createDefaultCustomMethods} from './defaultCustomMethods'; | ||
import {htmlElementSelectorCreator} from './htmlElementSelector'; | ||
import {locatorIdSelectorCreator} from './locatorIdSelector'; | ||
|
||
import type { | ||
CreateSelectorsOptions, | ||
GetTestAttrNameFn, | ||
SelectorCustomMethods, | ||
} from '../types/internal'; | ||
|
||
const createSelectorsWithCustomMethods = <CustomMethods extends SelectorCustomMethods = {}>( | ||
getTestAttrName: GetTestAttrNameFn, | ||
// force `this` to be Selector | ||
customMethods?: CustomMethods, | ||
): typeof selectorsWithCustomMethods => { | ||
const createSelector = createSelectorCreator(customMethods); | ||
|
||
const selectorsWithCustomMethods = { | ||
/** | ||
* Creates selector by locator and optional parameters. | ||
*/ | ||
createSelector, | ||
/** | ||
* Creates selector of page elements by CSS selector. | ||
*/ | ||
createSelectorByCss: createSelectorByCssCreator(createSelector), | ||
/** | ||
* Selector of page HTML element ("documentElement"). | ||
*/ | ||
htmlElementSelector: htmlElementSelectorCreator(createSelector), | ||
/** | ||
* Selector of locator elements by locator id. | ||
*/ | ||
locatorIdSelector: locatorIdSelectorCreator(createSelector, getTestAttrName), | ||
}; | ||
|
||
return selectorsWithCustomMethods; | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
export const createSelectors = ({getTestAttrName}: CreateSelectorsOptions) => | ||
createSelectorsWithCustomMethods(getTestAttrName, createDefaultCustomMethods(getTestAttrName)); |
Oops, something went wrong.