Skip to content

Commit 246e1f6

Browse files
committed
Playwright JavaScript Automation Framework
1 parent f1a298b commit 246e1f6

File tree

3 files changed

+324
-44
lines changed

3 files changed

+324
-44
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
{
2+
"subAreaMenuSelectors":{
3+
"DynamicSubAreaMenuSelector": "//ul[@aria-label='{0}']//span[text()='{1}']",
4+
"ServiceCaseMenuSelector": "//ul[@aria-label='Service']//span[text()='Cases']",
5+
"ServiceKBMenuSelector":"//ul[@aria-label='Service']//span[text()='Knowledge Search']"
6+
},
7+
"viewSelectors":{
8+
"MyActiveCasesSelector": "//button[@type='button']//span[text()='My Active Cases']",
9+
"ActiveCasesSelector": "//div[@aria-label='Views']//label[text()='Active Cases']",
10+
"DynamicViewSelector": "//*[@aria-label='Views']//label[text()='{0}']",
11+
"DefaultViewSelector":"//Span[text()='Open popup to change view.']/ancestor::button"
12+
},
13+
"advancedFilterSelectors":{
14+
"openFilterSelector": "//*[@id='open-advanced-filter']",
15+
"addRowFilterMenuSelector": "//*[@id='AddMenu0']",
16+
"addRowSelector":"//div[@role='menu']//span[text()='Add row']",
17+
"filterFieldMenuSelector": "//input[@placeholder='Select a field']//following-sibling::button",
18+
"filterFieldSelector":"//span[text()='{0}']/ancestor::button",
19+
"filterOperatorMenuSelector":"//input[@aria-label='Operator']//following-sibling::button",
20+
"filterOperatorSelector":"//div[@aria-label='Operator']//span[text()='Contains']",
21+
"filterOperatorContainsSelect":"//div[@aria-label='Operator']//span[text()='Contains']//ancestor::button",
22+
"filterInputTextSelector":"//input[@placeholder='Value']",
23+
"applyFilterButtonSelector":"//*[@id='applyFilters']"
24+
},
25+
"gridSelectors":{
26+
"gridFirstRowSelector": "[data-id='entity_control-pcf_grid_control_container'] [role='rowgroup'] [aria-rowindex='2'] [aria-colindex='2']",
27+
"cswGridRowSelector": "//div[@data-id='entity_control-pcf_grid_control_container']//descendant::a[@role='link']"
28+
},
29+
"FormSelectors": {
30+
"textAreaSelector":"//textarea[@aria-label='{0}']",
31+
"EditFormSelector": "//div[@data-id='editFormRoot']",
32+
"QuickCreateFormSelector": "[data-id='quickCreateRoot']"
33+
},
34+
"FormControlSelectors": {
35+
"CheckBoxSelect": "[data-id='{0}.fieldControl-checkbox-select']",
36+
"CheckBoxToggle": "input[data-id='{0}.fieldControl-checkbox-toggle']",
37+
"CurrencyTextBox": "[data-id='{0}.fieldControl-currency-text-input']",
38+
"DateTimeTextBox": "[data-id='{0}.fieldControl-date-time-input']",
39+
"DecimalTextBox": "[data-id='{0}.fieldControl-decimal-number-text-input']",
40+
"DurationComboBoxText": "[data-id='{0}.fieldControl-duration-combobox-text']",
41+
"EmailActionIcon": "[data-id='{0}.fieldControl-mail-action-icon']",
42+
"FloatNumberTextBox": "[data-id='{0}.fieldControl-floating-point-text-input']",
43+
"LangaugeSelect": "[data-id='{0}.fieldControl-language-picker-select']",
44+
"MailTextBox": "[data-id='{0}.fieldControl-mail-text-input']",
45+
"MultiSelect": "[data-id='{0}-FieldSectionItemContainer'] [title='{1}']",
46+
"MultiSelectTextBox": "[data-id='{0}-FieldSectionItemContainer'] [id='{0}_ledit']",
47+
"OptionSet": "[data-id='{0}.fieldControl-option-set-select']",
48+
"PhoneActionIcon": "[data-id='{0}.fieldControl-phone-action-icon']",
49+
"PhoneTextBox": "[data-id='{0}.fieldControl-phone-text-input']",
50+
"PicklistStatusComboBox": "[data-id='{0}.fieldControl-pickliststatus-comboBox-text']",
51+
"RecurranceDialogEvery": "[data-id='{0}.fieldControl.everylabelEveryControlID-whole-number-text-input']",
52+
"RichTextEditorBody": "[class^='cke_editable cke_editable_themed cke_contents_ltr']",
53+
"RichTextEditorBodyDescription": "[class^='cke_editable cke_editable_themed cke_contents_ltr'] > div",
54+
"RichTextEditorChildFrame": "[class^='cke_wysiwyg_frame']",
55+
"RichTextEditorFrame": "[data-id='{0}.fieldControl_container'] [class='fullPageContentEditorFrame']",
56+
"RecurrenceDialogTime": "[data-id='{0}.fieldControl_container'] input",
57+
"SubjectTree": "[data-id='{0}.fieldControl-subject-tree-input']",
58+
"TextBox": "[data-id='{0}.fieldControl-text-box-text']",
59+
"TickerActionIcon": "[data-id='{0}.fieldControl-ticker-action-icon']",
60+
"TickerTextBox": "[data-id='{0}.fieldControl-ticker-text-input']",
61+
"TimeComboBox": "[id='{0}_fabric_combobox-input']",
62+
"TimeZoneSelect": "[data-id='{0}.fieldControl-timezone-picker-select']",
63+
"ToggleContainer": "[data-id='{0}.fieldControl-toggle-container']",
64+
"TwoOptionListSelect": "[data-id='{0}.fieldControl-checkbox-select']",
65+
"TwoOptionListSelectContainer": "[data-id='{0}.fieldControl-checkbox-select-container']",
66+
"URLActionIcon": "[data-id='{0}.fieldControl-url-action-icon']",
67+
"URLTextBox": "[data-id='{0}.fieldControl-url-text-input']",
68+
"WholeNumberTextBox": "[data-id='{0}.fieldControl-whole-number-text-input']",
69+
"LookupButton":"//input[@data-id='{0}.fieldControl-LookupResultsDropdown_{0}_textInputBox_with_filter_new']//following-sibling::button",
70+
"TextBoxValueSelector": "//input[@data-id='{0}.fieldControl-text-box-text']"
71+
},
72+
"CommandBarFormButtonsSelectors": {
73+
"DynamicCommandBarButtonSelector": "//span[text()='{0}']//ancestor::button",
74+
"SaveButton": "//span[text()='Save']//ancestor::button"
75+
},
76+
"ResolveCaseFormSelector":{
77+
"DynamicTextBoxSelector":"//div[@data-id='{0}']//input[@aria-label='{1}']",
78+
"BillableTimeSelector":"//div[@data-id='billabletime_id']//input[@aria-label='Billable Time']",
79+
"DynamicFooterButtonSelector":"//div[@data-id='dialogFooter']//button[@aria-label='{0}']"
80+
},
81+
"cswSelectors":{
82+
"SiteMapButtonSelector": "//div[@aria-label='{0}']//button[@title='{1}']//span",
83+
"ServiceCaseMenuSelector": "//ul[@aria-label='Service']//span[text()='Cases']",
84+
"smartAssistKBSearchSelector": "//button[@aria-label='Knowledge search']//span",
85+
"smartAssistKBSearchTextboxSelector": "//*[@id='MscrmControls.KnowledgeControl.KnowledgeControl-ProductivityPanel-SearchTextBox']",
86+
"smartAssistKBSearchResultsSelector": "//ul[@id='MscrmControls.KnowledgeControl.KnowledgeControl-kbSearchResultsListId']//button[@title='Link this article to the current record']",
87+
"smartAssistKBArticleLinkedtoCaseSelector":"//ul[@id='MscrmControls.KnowledgeControl.KnowledgeControl-kbSearchResultsListId']//label[@aria-label='This article is linked to the Case record']",
88+
"smartAssistKBArticleUnlinktoCaseSelector": "//ul[@id='MscrmControls.KnowledgeControl.KnowledgeControl-kbSearchResultsListId']//button[@aria-label='Unlink']",
89+
"homeTabSelector":"//div[@role='tablist']//button[@aria-label='Home']",
90+
"dashboardTabSelector":"//div[@role='tablist']//span[text()='Customer Service Agent Dashboard']",
91+
"defaultCaseViewSelector":"//div[@aria-label='My Active Cases']",
92+
"gridSelector":"//div[@aria-label='CC_ReadonlyGrid_Name']//div[@tabindex='0']",
93+
"gridURLSelector":"//div[@aria-label='CC_ReadonlyGrid_Name']//div[@tabindex='0']//a[@tabindex='-1']",
94+
"globalSearchButtonSelector":"//button[@id='searchLauncher-button']",
95+
"searchInputSelector":"//input[@aria-label='Search box']",
96+
"searchFilterSelector":"//select[@data-id='categorized-search-filter-select']",
97+
"searchFilterOptionSelector":"//option[text()='Account']",
98+
"searchGridSelector":"//div[@id='MscrmControls.Grid.GridControl-account-MscrmControls.Grid.GridControl.account-GridListContainer']//li",
99+
"knowledgeSearchInputSelector":"//input[@id='MscrmControls.KnowledgeControl.KnowledgeControl-SearchTextBox']",
100+
"knowledgeSearchResultsSelector":"//div[@id='MscrmControls.KnowledgeControl.KnowledgeControl-SearchAndDataBlockContainer']//a[@role='button']",
101+
"inboxViewTabSelector":"//div[@role='tablist']//button[@aria-label='Inbox']"
102+
},
103+
"ReactivateCaseDialogSelector":{
104+
"ReactivateButtonSelector":"//div[@data-id='confirmdialog']//button[@aria-label='Reactivate']"
105+
},
106+
"GlobalCommandBarSelectors":{
107+
"globalQuickCreateButtonSelector":"//button[@aria-label='Create New Record. New']"
108+
},
109+
"CommandBarGlobalButtonsSelectors": {
110+
"AccountManager": "[id='mectrl_main_trigger']",
111+
"CommandBarSelector": "[data-id='topBar'] [data-lp-id*='commandbar-Global:']",
112+
"CustomButton": "[data-id='new.ApplicationRibbon.{0}.Button']",
113+
"Dynamics365TopBarButton": "[data-id='dynamics-button']",
114+
"ListOfButtons": "[data-id='topBar'] [data-lp-id*='commandbar-Global:'] li",
115+
"PersonalSettingsLauncher": "[data-id='topBar'] [data-id='personalSettingsLauncher']",
116+
"PersonalSettingsLauncherFlyoutAbout": "[data-id='__flyoutRootNode'] [data-id='personalSettingsLauncher'] [data-id='SettingsMenu.About']",
117+
"QuickCreateLauncher": "[data-id='topBar'] [data-id='quickCreateLauncher']",
118+
"QuickCreateLauncherFlyout": "[data-id='__flyoutRootNode'] [data-id='quickCreateLauncher']",
119+
"QuickCreateLauncherFlyoutButton": "[data-id*='quickCreateMenuButton_{0}']",
120+
"QuickCreateLauncherActivitiesFlyout": "[id*='id'][id*='_buttoncrm_header_global_flyout']",
121+
"TopBar": "[data-id='topBar']",
122+
"UserInformationLauncher": "[data-id='userInformationLauncher']"
123+
},
124+
"QuickCreateCaseFormSelectors":{
125+
"CustomerLookupSelector": "//div[@aria-label='Quick Create: Case']//input[@aria-label='Customer, Lookup']//following-sibling::button",
126+
"CaseTitleSelector":"//div[@aria-label='Quick Create: Case']//input[@aria-label='Case Title']",
127+
"DescriptionSelector":"//div[@aria-label='Quick Create: Case']//textarea[@aria-label='Description']",
128+
"SaveCloseButtonSelector": "//div[@aria-label='Quick Create: Case']//button[@data-id='quickCreateSaveAndCloseBtn']"
129+
}
130+
}
Lines changed: 134 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,139 @@
1-
export async function waitUntilAppIdle(page) {
2-
try {
3-
await page.waitForFunction(() => (window).UCWorkBlockTracker?.isAppIdle());
4-
} catch (e) {
5-
console.log("waitUntilIdle failed, ignoring.., error: " + e?.message);
6-
}
7-
}
8-
9-
export const stringFormat = (str, ...args) =>
10-
str.replace(/{(\d+)}/g, (match, index) => args[index].toString() || "");
1+
const { expect } = require('@playwright/test');
112

12-
export async function navigateToApps(page, appId, appName) {
13-
console.log('Navigate to ' + appName.toString() + ' - Start');
14-
await page.goto('/main.aspx?appid=' + appId.toString());
15-
await expect(page.getByRole('button', { name: appName })).toBeTruthy();
16-
console.log('Navigated to ' + appName.toString() + '- Success');
17-
}
3+
import exp from 'constants';
4+
import { subAreaMenuSelectors, CommandBarFormButtonsSelectors, viewSelectors, cswSelectors, gridSelectors } from '../selectors/commonselector.json';
5+
import { stringFormat, waitUntilAppIdle } from '../utils/Common';
186

197
/**
20-
* Load state conditions.
8+
* author: Testers Talk
219
*/
22-
export let LoadState
23-
; (function (LoadState) {
24-
LoadState["DomContentLoaded"] = "domcontentloaded"
25-
LoadState["Load"] = "load"
26-
LoadState["NetworkIdle"] = "networkidle"
27-
})(LoadState || (LoadState = {}))
28-
29-
export let TimeOut; (function (TimeOut) {
30-
TimeOut[(TimeOut["DefaultLoopWaitTime"] = 5000)] = "DefaultLoopWaitTime"
31-
TimeOut[(TimeOut["DefaultWaitTime"] = 30000)] = "DefaultWaitTime"
32-
TimeOut[(TimeOut["DefaultMaxWaitTime"] = 180000)] = "DefaultMaxWaitTime"
33-
TimeOut[(TimeOut["DefaultWaitTimeForValidation"] = 30000)] =
34-
"DefaultWaitTimeForValidation"
35-
TimeOut[(TimeOut["ElementWaitTime"] = 2000)] = "ElementWaitTime"
36-
TimeOut[(TimeOut["ExpectRetryDefaultWaitTime"] = 30000)] =
37-
"ExpectRetryDefaultWaitTime"
38-
TimeOut[(TimeOut["LoadTimeOut"] = 60000)] = "LoadTimeOut"
39-
TimeOut[(TimeOut["NavigationTimeout"] = 60000)] = "NavigationTimeout"
40-
TimeOut[(TimeOut["PageLoadTimeOut"] = 30000)] = "PageLoadTimeOut"
41-
TimeOut[(TimeOut["TestTimeout"] = 360000)] = "TestTimeout"
42-
TimeOut[(TimeOut["TestTimeoutMax"] = 6000000)] = "TestTimeoutMax"
43-
TimeOut[(TimeOut["OneMinuteTimeOut"] = 60000)] = "OneMinuteTimeOut"
44-
TimeOut[(TimeOut["TwoMinutesTimeout"] = 120000)] = "TwoMinutesTimeout"
45-
TimeOut[(TimeOut["ThreeMinutesTimeout"] = 180000)] = "ThreeMinutesTimeout"
46-
TimeOut[(TimeOut["FourMinutesTimeout"] = 240000)] = "FourMinutesTimeout"
47-
TimeOut[(TimeOut["FiveMinutesTimeout"] = 300000)] = "FiveMinutesTimeout"
48-
})(TimeOut || (TimeOut = {}))
10+
exports.CRMHelper = class CRMHelper {
11+
/**
12+
*
13+
* @param {import ('@playwright/test').Page} page
14+
*/
15+
constructor(page) {
16+
this.page = page;
17+
}
18+
19+
/**
20+
* author: Testers Talk
21+
*/
22+
async goToEntity(header, entity) {
23+
await expect(this.page.locator(stringFormat(subAreaMenuSelectors.ServiceCaseMenuSelector, header, entity))).toBeEnabled();
24+
await this.page.locator(stringFormat(subAreaMenuSelectors.ServiceCaseMenuSelector, header, entity)).click({ timeout: 20000 });
25+
await waitUntilAppIdle(this.page);
26+
}
27+
28+
/**
29+
* author: Testers Talk
30+
*/
31+
async clickOnCommandBarBtn(button) {
32+
await expect(this.page.locator(stringFormat(CommandBarFormButtonsSelectors.DynamicCommandBarButtonSelector, button))).toBeEnabled();
33+
await this.page.locator(stringFormat(CommandBarFormButtonsSelectors.DynamicCommandBarButtonSelector, button)).click();
34+
await waitUntilAppIdle(this.page);
35+
}
36+
37+
/**
38+
* author: Testers Talk
39+
*/
40+
async selectView(view) {
41+
await expect(this.page.locator(stringFormat(viewSelectors.DefaultViewSelector, view))).toBeEnabled();
42+
await this.page.locator(stringFormat(viewSelectors.DefaultViewSelector, view)).click();
43+
await waitUntilAppIdle(this.page);
44+
const viewMenu = this.page.locator(stringFormat(viewSelectors.DynamicViewSelector, view)).first();
45+
await viewMenu.waitFor({ state: 'attached' });
46+
await viewMenu.click();
47+
await waitUntilAppIdle(this.page);
48+
console.log('Selected view is : ' + view);
49+
}
50+
51+
/**
52+
* author: Testers Talk
53+
*/
54+
async searchRecord(search) {
55+
const locator = 'Filter by keyword';
56+
await this.page.getByPlaceholder(locator).clear();
57+
await this.page.getByPlaceholder(locator).fill(search);
58+
await this.page.getByPlaceholder(locator).press('Enter');
59+
await waitUntilAppIdle(this.page);
60+
}
61+
62+
/**
63+
* author: Testers Talk
64+
*/
65+
async openRecordBySingleClick(row, col) {
66+
const locator = "[data-id='cell-{0}-{1}']";
67+
await expect(this.page.locator(stringFormat(locator, row, col))).toBeEnabled();
68+
await this.page.locator(stringFormat(locator, row, col)).click();
69+
await waitUntilAppIdle(this.page);
70+
}
4971

72+
/**
73+
* author: Testers Talk
74+
*/
75+
async openRecordByDoubleClick(row, col) {
76+
const locator = "[data-id='cell-{0}-{1}']";
77+
await expect(this.page.locator(stringFormat(locator, row, col))).toBeEnabled();
78+
await this.page.locator(stringFormat(locator, row, col)).dblclick();
79+
await waitUntilAppIdle(this.page);
80+
}
81+
82+
/**
83+
* author: Testers Talk
84+
*/
85+
async clickOnTab(name) {
86+
await expect(this.page.locator(stringFormat(cswSelectors.TabSelector, name))).toBeEnabled();
87+
await this.page.locator(stringFormat(cswSelectors.TabSelector, name)).click();
88+
await waitUntilAppIdle(this.page);
89+
}
90+
91+
/**
92+
* author: Testers Talk
93+
*/
94+
async clickOnCommandBarSaveBtn() {
95+
await expect(this.page.locator(CommandBarFormButtonsSelectors.SaveButton)).toBeEnabled();
96+
await this.page.locator(CommandBarFormButtonsSelectors.SaveButton).click();
97+
await waitUntilAppIdle(this.page);
98+
}
99+
100+
/**
101+
* author: Testers Talk
102+
*/
103+
async openGridFirstRecord(count) {
104+
await expect(this.page.locator(gridSelectors.gridFirstRowSelector)).toBeVisible();
105+
await this.page.locator(gridSelectors.gridFirstRowSelector).click({ clickCount: count });
106+
await waitUntilAppIdle(this.page);
107+
}
108+
109+
/**
110+
* author: Testers Talk
111+
*/
112+
async clickBackButton() {
113+
const locator = 'Press Enter to go back.';
114+
await expect(this.page.getByLabel(locator)).toBeEnabled();
115+
await expect(this.page.getByLabel(locator)).click();
116+
await waitUntilAppIdle(this.page);
117+
}
118+
119+
/**
120+
* author: Testers Talk
121+
*/
122+
async selectGridRecordRadioBtn() {
123+
const locator = '.ms-Stack > .ms-Checkbox';
124+
await expect(this.page.getByLabel(locator).first()).toBeEnabled();
125+
await expect(this.page.getByLabel(locator).first()).click();
126+
await waitUntilAppIdle(this.page);
127+
}
128+
129+
/**
130+
* author: Testers Talk
131+
*/
132+
async selectDropdownValue(label, value) {
133+
await expect(this.page.getByLabel(label, { exact: true }).first()).toBeVisible();
134+
await this.page.getByLabel(label, { exact: true }).first().click();
135+
await expect(this.page.getByRole('option', { name: value }).first()).toBeVisible();
136+
await this.page.getByRole('option', { name: value }).first().click();
137+
await waitUntilAppIdle(this.page);
138+
}
139+
}

Playwright-JavaScript/utils/Common.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* author: Bakkappa N
3+
*/
4+
export async function waitUntilAppIdle(page) {
5+
try {
6+
await page.waitForFunction(() => (window).UCWorkBlockTracker?.isAppIdle());
7+
} catch (e) {
8+
console.log("waitUntilIdle failed, ignoring.., error: " + e?.message);
9+
}
10+
}
11+
12+
/**
13+
* author: Bakkappa N
14+
*/
15+
export const stringFormat = (str, ...args) =>
16+
str.replace(/{(\d+)}/g, (match, index) => args[index].toString() || "");
17+
18+
/**
19+
* author: Bakkappa N
20+
*/
21+
export async function navigateToApps(page, appId, appName) {
22+
console.log('Navigate to ' + appName.toString() + ' - Start');
23+
await page.goto('/main.aspx?appid=' + appId.toString());
24+
await expect(page.getByRole('button', { name: appName })).toBeTruthy();
25+
console.log('Navigated to ' + appName.toString() + '- Success');
26+
}
27+
28+
/**
29+
* author: Bakkappa N
30+
*/
31+
export let LoadState
32+
; (function (LoadState) {
33+
LoadState["DomContentLoaded"] = "domcontentloaded"
34+
LoadState["Load"] = "load"
35+
LoadState["NetworkIdle"] = "networkidle"
36+
})(LoadState || (LoadState = {}))
37+
38+
/**
39+
* author: Bakkappa N
40+
*/
41+
export let TimeOut; (function (TimeOut) {
42+
TimeOut[(TimeOut["DefaultLoopWaitTime"] = 5000)] = "DefaultLoopWaitTime"
43+
TimeOut[(TimeOut["DefaultWaitTime"] = 30000)] = "DefaultWaitTime"
44+
TimeOut[(TimeOut["DefaultMaxWaitTime"] = 180000)] = "DefaultMaxWaitTime"
45+
TimeOut[(TimeOut["DefaultWaitTimeForValidation"] = 30000)] =
46+
"DefaultWaitTimeForValidation"
47+
TimeOut[(TimeOut["ElementWaitTime"] = 2000)] = "ElementWaitTime"
48+
TimeOut[(TimeOut["ExpectRetryDefaultWaitTime"] = 30000)] =
49+
"ExpectRetryDefaultWaitTime"
50+
TimeOut[(TimeOut["LoadTimeOut"] = 60000)] = "LoadTimeOut"
51+
TimeOut[(TimeOut["NavigationTimeout"] = 60000)] = "NavigationTimeout"
52+
TimeOut[(TimeOut["PageLoadTimeOut"] = 30000)] = "PageLoadTimeOut"
53+
TimeOut[(TimeOut["TestTimeout"] = 360000)] = "TestTimeout"
54+
TimeOut[(TimeOut["TestTimeoutMax"] = 6000000)] = "TestTimeoutMax"
55+
TimeOut[(TimeOut["OneMinuteTimeOut"] = 60000)] = "OneMinuteTimeOut"
56+
TimeOut[(TimeOut["TwoMinutesTimeout"] = 120000)] = "TwoMinutesTimeout"
57+
TimeOut[(TimeOut["ThreeMinutesTimeout"] = 180000)] = "ThreeMinutesTimeout"
58+
TimeOut[(TimeOut["FourMinutesTimeout"] = 240000)] = "FourMinutesTimeout"
59+
TimeOut[(TimeOut["FiveMinutesTimeout"] = 300000)] = "FiveMinutesTimeout"
60+
})(TimeOut || (TimeOut = {}))

0 commit comments

Comments
 (0)