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

test(quantic): SFINT-5832 Sort E2E tests migrate from Cypress to Playwright #4777

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {SortObject} from './sortObject';
SimonMilord marked this conversation as resolved.
Show resolved Hide resolved
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
import {
searchRequestRegex,
insightSearchRequestRegex,
} from '../../../../../../playwright/utils/requests';
import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
import {useCaseEnum} from '../../../../../../playwright/utils/useCase';

const pagerUrl = 's/quantic-sort';
lvu285 marked this conversation as resolved.
Show resolved Hide resolved

interface SortOptions {}

type QuanticSortE2EFixtures = {
sort: SortObject;
sortCustom: SortObject;
sortInvalid: SortObject;
search: SearchObject;
options: Partial<SortOptions>;
};

type QuanticSortE2ESearchFixtures = QuanticSortE2EFixtures & {
urlHash: string;
};

type QuanticSortE2EInsightFixtures = QuanticSortE2ESearchFixtures & {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
insightSetup: InsightSetupObject;
};

export const testSearch = quanticBase.extend<QuanticSortE2ESearchFixtures>({
options: {},
urlHash: '',
search: async ({page}, use) => {
await use(new SearchObject(page, searchRequestRegex));
},

sort: async ({page, options, configuration, search, urlHash}, use) => {
await page.goto(urlHash ? `${pagerUrl}#${urlHash}` : pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
configuration.configure(options);
await search.waitForSearchResponse();
await use(new SortObject(page));
},

sortCustom: async ({page, options, configuration, search, urlHash}, use) => {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page.goto(urlHash ? `${pagerUrl}#${urlHash}` : pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page.getByRole('button', {name: 'Add Custom Sort Options'}).click();
configuration.configure(options);
await search.waitForSearchResponse();
await use(new SortObject(page));
},

sortInvalid: async ({page, options, configuration, search, urlHash}, use) => {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page.goto(urlHash ? `${pagerUrl}#${urlHash}` : pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page
.getByRole('button', {name: 'Add Invalid Custom Sort Options'})
.click();
configuration.configure(options);
await search.waitForSearchResponse();
await use(new SortObject(page));
},
});

export const testInsight = quanticBase.extend<QuanticSortE2EInsightFixtures>({
options: {},
search: async ({page}, use) => {
await use(new SearchObject(page, insightSearchRequestRegex));
},

insightSetup: async ({page}, use) => {
await use(new InsightSetupObject(page));
},

sort: async ({page, options, search, configuration, insightSetup}, use) => {
await page.goto(pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
configuration.configure({...options, useCase: useCaseEnum.insight});
await insightSetup.waitForInsightInterfaceInitialization();
await search.performSearch();
await search.waitForSearchResponse();
await use(new SortObject(page));
},

sortCustom: async (
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
{page, options, search, configuration, insightSetup},
use
) => {
await page.goto(pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page.getByRole('button', {name: 'Add Custom Sort Options'}).click();
configuration.configure({...options, useCase: useCaseEnum.insight});
await insightSetup.waitForInsightInterfaceInitialization();
await search.performSearch();
await search.waitForSearchResponse();
await use(new SortObject(page));
},

sortInvalid: async (
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
{page, options, search, configuration, insightSetup},
use
) => {
await page.goto(pagerUrl);
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await page
.getByRole('button', {name: 'Add Invalid Custom Sort Options'})
.click();
configuration.configure({...options, useCase: useCaseEnum.insight});
await insightSetup.waitForInsightInterfaceInitialization();
await search.performSearch();
await search.waitForSearchResponse();
await use(new SortObject(page));
},
});

export {expect} from '@playwright/test';
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {testSearch, testInsight, expect} from './fixture';
import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';

const newestDateSort = 'date descending';
const viewsDescendingSort = '@ytviewcount descending';
const defaultSortLabels = ['Relevancy', 'Newest', 'Oldest'];

const fixtures = {
search: testSearch,
insight: testInsight,
};

useCaseTestCases.forEach((useCase) => {
let test = fixtures[useCase.value];

test.describe(`quantic sort ${useCase.label}`, () => {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
test.describe(`when changing sort option to Newest`, () => {
test('should trigger a new search and log analytics', async ({
sort,
search,
}) => {
const searchResponsePromise = search.waitForSearchResponse();
await sort.clickSortDropDown();
await sort.clickSortButton('Newest');
const searchResponse = await searchResponsePromise;
const {sortCriteria} = searchResponse.request().postDataJSON();
expect(sortCriteria).toBe(newestDateSort);
await sort.waitForSortUaAnalytics(newestDateSort);
});
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

In my opinion for your test to be robust, you shouldn't rely on "Newest" to be the test option.

Your test should be a bit more agnostic to which Sort options exist.

Example

Suggested change
test.describe(`when changing sort option to Newest`, () => {
test('should trigger a new search and log analytics', async ({
sort,
search,
}) => {
const searchResponsePromise = search.waitForSearchResponse();
await sort.clickSortDropDown();
await sort.clickSortButton('Newest');
const searchResponse = await searchResponsePromise;
const {sortCriteria} = searchResponse.request().postDataJSON();
expect(sortCriteria).toBe(newestDateSort);
await sort.waitForSortUaAnalytics(newestDateSort);
});
});
test.describe(`when changing sort option`, () => {
test('should trigger a new search and log analytics', async ({
sort,
search,
}) => {
const searchResponsePromise = search.waitForSearchResponse();
await sort.clickSortDropDown();
const {expectedSortName, expectedSortValue} = await sort.getSortAtPosition(1); // Assuming 0 is the currently selected option, 1 should be the one after that.
await sort.clickSortButton(1);
const searchResponse = await searchResponsePromise;
const {sortCriteria} = searchResponse.request().postDataJSON();
expect(sortCriteria).toBe(expectedSortValue);
await sort.waitForSortUaAnalytics(expectedSortValue);
});
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, for e2e test small happy path like this, I don't think we need it too robust, as we also want to be sure the order of 'sort' is the same as what we expect at this moment

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's just the thing, I see it as the opposite, if the order of sort changes, we don't want the test to fail.

We're testing that the sort you selected is what actually gets sent in UA and in the search response.

The order doesn't matter, it could be the 2nd, 3rd, whatever. It just needs to be the same.

It is what we do in our other tests, so please make that small change so our test doesn't break if the ordering of the sort changes. :)


test.describe(`when changing custom sort to "Views Descending`, () => {
test('should trigger a new search and log analytics', async ({
sortCustom,
search,
}) => {
const searchResponsePromise = search.waitForSearchResponse();
await sortCustom.clickSortDropDown();
await sortCustom.clickSortButton('Views Descending');
const searchResponse = await searchResponsePromise;
const {sortCriteria} = searchResponse.request().postDataJSON();
expect(sortCriteria).toBe(viewsDescendingSort);
await sortCustom.waitForSortUaAnalytics(viewsDescendingSort);
});
});
lvu285 marked this conversation as resolved.
Show resolved Hide resolved

lvu285 marked this conversation as resolved.
Show resolved Hide resolved
test.describe(`when the custom option passed has an invalid property`, () => {
test('should display an error message instead of the quanticSort component', async ({
sortInvalid,
}) => {
await sortInvalid.invalidSortMessage();
});
});

test.describe('when testing accessibility', () => {
test('should be accessible to keyboard', async ({sort}) => {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
let selectedSortLabel = await sort.sortDropDown.textContent();
expect(selectedSortLabel).toEqual(defaultSortLabels[0]);

// Selecting the next sort using the ArrowDown, then ENTER key
await sort.focusSortDropDownEnter();
await sort.selectSortButtonKeyboard();
selectedSortLabel = await sort.sortDropDown.textContent();
expect(selectedSortLabel).toEqual(defaultSortLabels[1]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Again here, I would go fetch what the loaded sorts are available on the page instead of relying on the default sorts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same as above, we want to be sure the sort displays in correct order

Copy link
Collaborator

Choose a reason for hiding this comment

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

Order of sorts is not in the contract of our component. It's a parameter we receive and then display. We have unit tests to check that the sort are displayed in the order we've received them.

The e2e test shouldn't rely on the order of sort options to pass or fail. Please make this small change :)


// Selecting the next sort using the ArrowDown, then ENTER key
await sort.focusSortDropDownEnter();
await sort.selectSortButtonKeyboard();
selectedSortLabel = await sort.sortDropDown.textContent();
expect(selectedSortLabel).toEqual(defaultSortLabels[2]);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {Locator, Page, Request} from '@playwright/test';
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
import {isUaSearchEvent} from '../../../../../../playwright/utils/requests';

export class SortObject {
constructor(public page: Page) {
this.page = page;
}

get sortDropDown(): Locator {
return this.page.getByRole('combobox', {name: 'Sort by'});
}

get sortPreviewHeader(): Locator {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
return this.page.getByRole('button', {name: 'Preview'});
}

get invalidMessage(): Locator {
return this.page
.locator('p')
.filter({hasText: 'Custom sort options configuration is invalid.'});
}

sortButton(buttonName: string): Locator {
return this.page.getByRole('option', {name: buttonName});
}

async clickSortDropDown(): Promise<void> {
await this.sortDropDown.click();
}

async focusSortDropDownEnter(): Promise<void> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does the "focusSortDropDownEnter" mean here?
The function clicks on the preview button and then presses Tab?

await this.sortPreviewHeader.click();
await this.page.keyboard.press('Tab');
}

async clickSortButton(buttonName: string): Promise<void> {
await this.sortButton(buttonName).click();
}

async selectSortButtonKeyboard(): Promise<void> {
lvu285 marked this conversation as resolved.
Show resolved Hide resolved
await this.page.keyboard.press('Enter');
await this.sortButton('Oldest').isVisible();
await this.page.waitForTimeout(500);
await this.page.keyboard.press('ArrowDown');
await this.page.keyboard.press('Enter');
Copy link
Collaborator

Choose a reason for hiding this comment

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

The name of this function doesn't describe what is being done with the steps within this function, what are you trying to accomplish with that?

}

async invalidSortMessage(): Promise<void> {
await this.invalidMessage.isVisible();
}

async waitForSortUaAnalytics(eventValue: any): Promise<Request> {
const uaRequest = this.page.waitForRequest((request) => {
if (isUaSearchEvent(request)) {
const requestBody = request.postDataJSON();
const expectedFields = {
actionCause: 'resultsSort',
customData: {
resultsSortBy: eventValue,
},
};

const validateObject = (obj: any, expectedResult: any): boolean =>
Object.entries(expectedResult).every(([key, value]) =>
value && typeof value === 'object'
? validateObject(obj?.[key], value)
: obj?.[key] === value
);

return validateObject(requestBody, expectedFields);
}
return false;
});
return uaRequest;
}
}
Loading