Skip to content

Commit

Permalink
refactoring of "che" test automation framework classes according to C…
Browse files Browse the repository at this point in the history
…ODE_STYLE.md

Signed-off-by: mdolhalo <[email protected]>
  • Loading branch information
mdolhalo committed Sep 4, 2023
1 parent 41d1364 commit babeb1b
Show file tree
Hide file tree
Showing 52 changed files with 836 additions and 566 deletions.
16 changes: 15 additions & 1 deletion tests/e2e/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,21 @@ module.exports = {
'@typescript-eslint/member-ordering': [
'error',
{
classes: ['field', 'constructor', 'method']
default: [
'static-field',
'public-field',
'instance-field',
'protected-field',
'private-field',
'abstract-field',
'constructor',
'public-static-method',
'protected-static-method',
'private-static-method',
'public-method',
'protected-method',
'private-method'
]
}
],
'@typescript-eslint/explicit-function-return-type': [
Expand Down
144 changes: 144 additions & 0 deletions tests/e2e/CODE_STYLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Coding Standards and Conventions

### Introducing

#### Why are coding standards important?

Coding standards offer several advantages, including:

1. Increase Code Quality: By adhering to coding standards, developers can create code that is more secure, efficient,
maintainable, and uniform. This, in turn, can result in fewer errors and improved overall performance.

2. Improved Readability and Maintainability: Coding standards contribute to code that is more comprehensible and easier
to maintain. Consistently formatted code aids other developers in comprehending and modifying it, saving time and
reducing the likelihood of introducing errors.

3. Accelerated Development: The adherence to coding standards can expedite the development process. When developers
adhere to a predefined set of guidelines, they can produce code more swiftly and with fewer mistakes. Additionally,
uniform code formatting facilitates the identification and resolution of issues.

4. Better Scalability: Coding standards facilitate the creation of scalable code, simplifying the incorporation of new
features or updates. Consistent coding practices also streamline code maintenance as the codebase expands.

5. Elevated Collaboration and Communication: Uniform guidelines encourage better understanding and manipulation of code
written by fellow developers. This fosters smoother teamwork and facilitates the sharing of code.

6. Consistency Across Projects: The adoption of coding standards guarantees a consistent coding approach across various
projects. This simplifies the task of upholding code quality, transitioning between tasks, and fostering
collaborative work.

### Automated tools

Automated lint checking and code format performs with ESLint and Prettier tools before every commit using Husky
pre-commit hook.
Full set of rules can be found:

- [.eslintrc](.eslintrc.js)
- [.prettierrc](.prettierrc.json)

### Preferable code style

1. Page-object and util classes

1. ✔ Class declaration using dependency injection (inversify library)

```
@injectable()
export class BrowserTabsUtil {
constructor(@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { }
```

2. Public methods

- ✔ Declare public methods without "public "keyword
- ✔ Add Logger.debug() inside method to log its name (with optional message)

```
async switchToWindow(windowHandle: string): Promise<void> {
Logger.debug(); // logs BrowserUtils.sswitchToWindow
await this.driverHelper.getDriver().switchTo().window(windowHandle);
}
```

3. Locators

- ✔ For static locators - private static readonly fields type of By

```
private static readonly FACTORY_URL_LOCATOR: By = By.xpath('//input[@id="git-repo-url"]');
```

- ✔ For dynamic locators - private methods which returns By

```
private getExpandedActionsLocator(workspaceName: string): By {
return By.xpath(`${this.getWorkspaceListItemLocator(workspaceName)}//button[@aria-label='Actions' and @aria-expanded='true']`);
}
```

- ✗ Avoid to declare locators as constant in methods

```
async waitTitleContains(expectedText: string, timeout: number = TimeoutConstants.TS_COMMON_DASHBOARD_WAIT_TIMEOUT): Promise<void> {
Logger.debug();
const pageTitleLocator: By = By.xpath(`//h1[contains(text(), '${expectedText}')]`);
await this.driverHelper.waitVisibility(pageTitleLocator, timeout);
}
```

#### Page object sample:

```
import { e2eContainer } from '../../configs/inversify.config';
@injectable()
export class OcpMainPage {
private static readonly MAIN_PAGE_HEADER_LOCATOR: By = By.id('page-main-header');
private static readonly SELECT_ROLE_BUTTON_LOCATOR: By = By.xpath('//*[@data-test-id="perspective-switcher-toggle"]');
constructor(
@inject(CLASSES.DriverHelper) private readonly driverHelper: DriverHelper) { }
async waitOpenMainPage(): Promise<void> {
Logger.debug();
await this.driverHelper.waitVisibility(OcpMainPage.MAIN_PAGE_HEADER_LOCATOR, TimeoutConstants.TS_SELENIUM_LOAD_PAGE_TIMEOUT);
}
private getProjectDropdownItemLocator(projectName: string): By {
return By.xpath(`//button//*[text()="${projectName}"]`);
}
}
```

2. Mocha framework

- ✔ TDD framework (`suit()`, `test()`)
- ✔ Inject class instances, declare all test data inside test `suit()` function to avoid unnecessary code execution if test suit will not be run

```
suite('name', function(): void {
const webCheCodeLocators: Locators = e2eContainer.get(CLASSES.CheCodeLocatorLoader);
// test specific data
const gitImportReference: string = 'pipeline';
```

- ✗ Don`t use arrow functions in test declaration https://mochajs.org/#arrow-functions
- ✔ Specs which don`t use browser should have API in name ending (like EmptyWorkspaceAPI.spec)
- ✗ Don\`t create scripts in package.json for each test. Instead of it use [dynamic config](configs/mocharc.ts), `mocha --spec` or `mocha --grep` flags to run specific test.
- ✔ Use test [./constants](constants) to make test flexible

3. Packages

1. Add packages as dev dependencies
2. If any changes re-create package-lock.json before push

4. Comments
1. If some code commented or added as workaround mark it as `//todo` with number of issue to get possibility to find it quickly
```
// todo commented due to issue crw-1010
```
31 changes: 26 additions & 5 deletions tests/e2e/configs/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
**********************************************************************/

import 'reflect-metadata';
import { Container } from 'inversify';
import { Container, decorate, injectable, unmanaged } from 'inversify';
import { IDriver } from '../driver/IDriver';
import { ChromeDriver } from '../driver/ChromeDriver';
import { CLASSES, TYPES } from './inversify.types';
import { CLASSES, EXTERNAL_CLASSES, TYPES } from './inversify.types';
import { TestWorkspaceUtil } from '../utils/workspace/TestWorkspaceUtil';
import { IOcpLoginPage } from '../pageobjects/login/interfaces/IOcpLoginPage';
import { OcpUserLoginPage } from '../pageobjects/login/openshift/OcpUserLoginPage';
Expand Down Expand Up @@ -44,8 +44,21 @@ import { KubernetesLoginPage } from '../pageobjects/login/kubernetes/KubernetesL
import { DexLoginPage } from '../pageobjects/login/kubernetes/DexLoginPage';
import { OAUTH_CONSTANTS } from '../constants/OAUTH_CONSTANTS';
import { BASE_TEST_CONSTANTS, Platform } from '../constants/BASE_TEST_CONSTANTS';
import { CheCodeLocatorLoader } from '../pageobjects/ide/CheCodeLocatorLoader';
import { LocatorLoader } from 'monaco-page-objects/out/locators/loader';
import { OauthPage } from '../pageobjects/git-providers/OauthPage';
import { DevfilesRegistryHelper } from '../utils/DevfilesRegistryHelper';
import { Main as Generator } from '@eclipse-che/che-devworkspace-generator/lib/main';
import { ContainerTerminal, KubernetesCommandLineToolsExecutor } from '../utils/KubernetesCommandLineToolsExecutor';
import { ShellExecutor } from '../utils/ShellExecutor';

const e2eContainer: Container = new Container({ defaultScope: 'Transient' });
decorate(injectable(), Generator);
decorate(injectable(), LocatorLoader);
decorate(unmanaged(), LocatorLoader, 0);
decorate(unmanaged(), LocatorLoader, 1);
decorate(unmanaged(), LocatorLoader, 2);

const e2eContainer: Container = new Container({ defaultScope: 'Transient', skipBaseClassChecks: true });

e2eContainer.bind<IDriver>(TYPES.Driver).to(ChromeDriver).inSingletonScope();
e2eContainer.bind<ITestWorkspaceUtil>(TYPES.WorkspaceUtil).to(TestWorkspaceUtil);
Expand All @@ -59,11 +72,12 @@ e2eContainer.bind<WorkspaceDetails>(CLASSES.WorkspaceDetails).to(WorkspaceDetail
e2eContainer.bind<ScreenCatcher>(CLASSES.ScreenCatcher).to(ScreenCatcher);
e2eContainer.bind<OcpLoginPage>(CLASSES.OcpLoginPage).to(OcpLoginPage);
e2eContainer.bind<DexLoginPage>(CLASSES.DexLoginPage).to(DexLoginPage);

e2eContainer.bind<CheCodeLocatorLoader>(CLASSES.CheCodeLocatorLoader).to(CheCodeLocatorLoader);
e2eContainer.bind<LocatorLoader>(CLASSES.LocatorLoader).to(LocatorLoader);
e2eContainer.bind<OauthPage>(CLASSES.OauthPage).to(OauthPage);
e2eContainer.bind<OcpMainPage>(CLASSES.OcpMainPage).to(OcpMainPage);
e2eContainer.bind<OcpImportFromGitPage>(CLASSES.OcpImportFromGitPage).to(OcpImportFromGitPage);
e2eContainer.bind<OcpApplicationPage>(CLASSES.OcpApplicationPage).to(OcpApplicationPage);

e2eContainer.bind<CheApiRequestHandler>(CLASSES.CheApiRequestHandler).to(CheApiRequestHandler);
e2eContainer.bind<CreateWorkspace>(CLASSES.CreateWorkspace).to(CreateWorkspace);
e2eContainer.bind<ProjectAndFileTests>(CLASSES.ProjectAndFileTests).to(ProjectAndFileTests);
Expand All @@ -72,6 +86,13 @@ e2eContainer.bind<StringUtil>(CLASSES.StringUtil).to(StringUtil);
e2eContainer.bind<ApiUrlResolver>(CLASSES.ApiUrlResolver).to(ApiUrlResolver);
e2eContainer.bind<WorkspaceHandlingTests>(CLASSES.WorkspaceHandlingTests).to(WorkspaceHandlingTests);
e2eContainer.bind<RedHatLoginPage>(CLASSES.RedHatLoginPage).to(RedHatLoginPage);
e2eContainer.bind<DevfilesRegistryHelper>(CLASSES.DevfilesRegistryHelper).to(DevfilesRegistryHelper);
e2eContainer.bind<KubernetesCommandLineToolsExecutor>(CLASSES.KubernetesCommandLineToolsExecutor).to(KubernetesCommandLineToolsExecutor);
e2eContainer.bind<ShellExecutor>(CLASSES.ShellExecutor).to(ShellExecutor);
e2eContainer.bind<ContainerTerminal>(CLASSES.ContainerTerminal).to(ContainerTerminal);

e2eContainer.bind<Generator>(EXTERNAL_CLASSES.Generator).to(Generator);
e2eContainer.bind<LocatorLoader>(EXTERNAL_CLASSES.LocatorLoader).to(LocatorLoader);

if (BASE_TEST_CONSTANTS.TS_PLATFORM === Platform.OPENSHIFT) {
if (OAUTH_CONSTANTS.TS_SELENIUM_VALUE_OPENSHIFT_OAUTH) {
Expand Down
20 changes: 17 additions & 3 deletions tests/e2e/configs/inversify.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ const TYPES: any = {
OcpLogin: Symbol.for('OcpLogin'),
WorkspaceUtil: Symbol.for('WorkspaceUtil'),
IAuthorizationHeaderHandler: Symbol.for('IAuthorizationHeaderHandler'),
ITokenHandler: Symbol.for('ITokenHandler')
ITokenHandler: Symbol.for('ITokenHandler'),
IKubernetesCommandLineToolsExecutor: Symbol.for('IKubernetesCommandLineToolsExecutor'),
IContextParams: Symbol.for('IContextParams')
};

const CLASSES: any = {
Expand All @@ -38,7 +40,19 @@ const CLASSES: any = {
OcpRedHatLoginPage: 'OcpRedHatLoginPage',
OcpApplicationPage: 'OcpApplicationPage',
OcpMainPage: 'OcpMainPage',
OcpImportFromGitPage: 'OcpImportFromGitPage'
OcpImportFromGitPage: 'OcpImportFromGitPage',
CheCodeLocatorLoader: 'CheCodeLocatorLoader',
LocatorLoader: 'LocatorLoader',
OauthPage: 'OauthPage',
DevfilesRegistryHelper: 'DevfilesRegistryHelper',
KubernetesCommandLineToolsExecutor: 'KubernetesCommandLineToolsExecutor',
ShellExecutor: 'ShellExecutor',
ContainerTerminal: 'ContainerTerminal'
};

export { TYPES, CLASSES };
const EXTERNAL_CLASSES: any = {
Generator: 'Generator',
LocatorLoader: 'LocatorLoader'
};

export { TYPES, CLASSES, EXTERNAL_CLASSES };
2 changes: 1 addition & 1 deletion tests/e2e/configs/mocharc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {
reporter: 'dist/utils/CheReporter.js',
ui: 'tdd',
require: ['dist/specs/MochaHooks.js', 'ts-node/register'],
bail: true,
bail: false,
'full-trace': true,
spec:
// variable MOCHA_DIRECTORY uses in command "test-all-devfiles" and sets up automatically.
Expand Down
14 changes: 1 addition & 13 deletions tests/e2e/constants/CHROME_DRIVER_CONSTANTS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
export const CHROME_DRIVER_CONSTANTS: {
TS_SELENIUM_RESOLUTION_WIDTH: number;
TS_SELENIUM_W3C_CHROME_OPTION: boolean;
TS_SELENIUM_HEADLESS: boolean;
TS_USE_WEB_DRIVER_FOR_TEST: boolean;
TS_SELENIUM_RESOLUTION_HEIGHT: number;
TS_SELENIUM_LAUNCH_FULLSCREEN: boolean;
TS_SELENIUM_REMOTE_DRIVER_URL: string;
} = {
Expand Down Expand Up @@ -40,15 +38,5 @@ export const CHROME_DRIVER_CONSTANTS: {
/**
* run browser with an enabled or disabled W3C protocol (on Chrome 76 and upper, it is enabled by default), "true" by default.
*/
TS_SELENIUM_W3C_CHROME_OPTION: process.env.TS_SELENIUM_W3C_CHROME_OPTION !== 'false',

/**
* browser width resolution, "1920" by default.
*/
TS_SELENIUM_RESOLUTION_WIDTH: Number(process.env.TS_SELENIUM_RESOLUTION_WIDTH) || 1920,

/**
* browser height resolution, "1080" by default.
*/
TS_SELENIUM_RESOLUTION_HEIGHT: Number(process.env.TS_SELENIUM_RESOLUTION_HEIGHT) || 1080
TS_SELENIUM_W3C_CHROME_OPTION: process.env.TS_SELENIUM_W3C_CHROME_OPTION !== 'false'
};
7 changes: 0 additions & 7 deletions tests/e2e/driver/ChromeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ export class ChromeDriver implements IDriver {
return this.driver as ThenableWebDriver;
}

async setWindowSize(): Promise<void> {
await (this.driver as ThenableWebDriver)
.manage()
.window()
.setSize(CHROME_DRIVER_CONSTANTS.TS_SELENIUM_RESOLUTION_WIDTH, CHROME_DRIVER_CONSTANTS.TS_SELENIUM_RESOLUTION_HEIGHT);
}

private getDriverOptions(): Options {
let options: Options = new Options()
.addArguments('--no-sandbox')
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export * from './utils/BrowserTabsUtil';
export * from './utils/DevfilesRegistryHelper';
export * from './utils/DevWorkspaceConfigurationHelper';
export * from './utils/DriverHelper';
export * from './utils/IContextParams';
export * from './utils/IKubernetesCommandLineToolsExecutor';
export * from './utils/KubernetesCommandLineToolsExecutor';
export * from './utils/Logger';
export * from './utils/request-handlers/CheApiRequestHandler';
Expand Down
22 changes: 14 additions & 8 deletions tests/e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions tests/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@
"selenium-webdriver": "4.4.0",
"shelljs": "^0.8.5",
"ts-node": "^10.9.1",
"typescript": "3.9.9",
"typescript": "4.9.4",
"vscode-extension-tester-locators": "3.1.0",
"yaml": "^2.2.2"
},
"dependencies": {
"@eclipse-che/api": "latest",
"inversify": "5.0.1",
"inversify": "6.0.1",
"reflect-metadata": "0.1.13"
},
"resolutions": {
Expand Down
Loading

0 comments on commit babeb1b

Please sign in to comment.