Skip to content

Commit

Permalink
feat: wdi5 code generator (#17)
Browse files Browse the repository at this point in the history
* fix: typo

* refactor: case

* fix: rum unused import

* chore(test): prep code scenario build + test infrastructure

* test(assets): add sample json journey

* test: basic skeletion

* docs: npm script tasks

* refactor: rename test files per scope

* refactor: simplify runtime

* wip: prep input step

* feat(wdi5): no difference btw paged and single step

for now :)

* feat(click-step): wdi5 code

* feat(wdi5,selector): support boolean formatting

* test: add exists/validate step

* test: prep input step

* refactor: clean up

* refactor: cleanup

* feat: generates pages

* fix: test correct method

* refactor: member viz

* feat: accompany indent

* feat: page journey building

* refactor: reformat

* test: skip scenario due to still some scenario export flaws

* docs: parts of journey

* fix: input step code generation
  • Loading branch information
vobu authored Oct 17, 2022
1 parent f18ea44 commit 51c83ea
Show file tree
Hide file tree
Showing 15 changed files with 1,348 additions and 54 deletions.
3 changes: 3 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ Thumbs.db

# Documentation
/documentation

# test-time: code service
dist-codeservice
4 changes: 2 additions & 2 deletions app/doc_assets/page_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ paths:
get:
tags:
- "controls"
summary: "Find controls on page by thier attributes"
description: "Multiple controls can be searched filtered by thier attributes"
summary: "Find controls on page by their attributes"
description: "Multiple controls can be searched filtered by their attributes"
operationId: "getControls"
produces:
- "application/json"
Expand Down
4 changes: 4 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
"build": "ng build",
"build:dev": "ng build --configuration development",
"build:watch": "ng build --watch",
"//build:codeService": "typescript-only build (no angular/webpack) to test code generation with",
"build:codeService": "tsc -p ./tsconfig-testing-only.json",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"//test:codeService": "run build:codeService first",
"test:codeService": "NODE_PATH=./dist-codeservice node --test test/",
"doc": "npx compodoc",
"doc:serve": "npx compodoc -s",
"docu:coverage": "npx compodoc",
Expand Down
2 changes: 1 addition & 1 deletion app/src/app/scenario/codeService/codeService.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Wdi5SingleStepStrategy from './strategies/wdi5/Wdi5SingleStepStrategy';

export enum CodeStyles {
OPA5 = 'OPA5',
WDI5 = 'Wdi5',
WDI5 = 'wdi5',
UNDEFINDED = 'UNDEFINED',
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
import { Step, StepType } from 'src/app/classes/Step';
import { TestScenario } from 'src/app/classes/testScenario';
import { InputStep, Step, StepType } from 'src/app/classes/Step';
import { Page, TestScenario } from 'src/app/classes/testScenario';
import CodeStrategy from '../StrategyInterface';
import Wdi5CodePageBuilder from './Wdi5PageBuilder';
import Wdi5RootPageBuilder from './Wdi5RootPageBuilder';
import Wdi5PageBuilder from './Wdi5PageBuilder';
import Wdi5SingleStepStrategy from './Wdi5SingleStepStrategy';

export default class Wdi5CodeStrategy implements CodeStrategy {
// we treat each "page" as part of the entire journey and slice it up accordingly
generateTestCode(scenario: TestScenario): any[] {
const codes: any[] = [];
const pages: { [key: string]: Wdi5CodePageBuilder } = {};
pages['Page'] = new Wdi5RootPageBuilder();

Object.entries(pages).forEach((entry: [string, Wdi5CodePageBuilder]) => {
scenario.testPages.forEach((page: Page) => {
const code = {
title: `${entry[0]}-Page`,
code: entry[1].generate(),
title: `part ${page.id} of Journey`,
code: new Wdi5PageBuilder(
page,
page.view.relativeViewName,
`#/${page.view.relativeViewName}`
).generate(),
};
//@TODO: Generate the correct Journey and wdi5 page codes
codes.push(code);
});
return codes;
}
generateStepCode(step: Step): string {

// no difference in wdi5 btw a step in a page or a standalone step

generateStepCode(step: Step, indent: number = 0): string {
switch (step.actionType) {
case StepType.Click:
return Wdi5SingleStepStrategy.generateSinglePressStep(step);
return Wdi5SingleStepStrategy.generateSinglePressStep(step, indent);
case StepType.Validation:
return Wdi5SingleStepStrategy.generateSingleExistsStep(step, indent);
case StepType.Input:
return Wdi5SingleStepStrategy.generateSingleInputStep(step as InputStep, indent);
default:
return 'Unknown StepType';
}
}

// no difference in wdi5 btw a step in a page or a standalone step
generatePagedStepCode(step: Step, viewName?: string | undefined): string {
return '@TODO';
return this.generateStepCode(step);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Page } from 'src/app/classes/testScenario';

export default abstract class Wdi5IPageBuilder {
protected _page: Page;
constructor(page: Page) {
this._page = page;
}
public abstract generate(): string;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,48 @@
import StringBuilder from 'src/app/classes/StringBuilder';
import { Page } from 'src/app/classes/testScenario';
import Wdi5CodeStrategy from './Wdi5CodeStrategy';
import Wdi5IPageBuilder from './Wdi5IPageBuilder';
import Wdi5SingleStepStrategy from './Wdi5SingleStepStrategy';

export default abstract class Wdi5CodePageBuilder {
private _page: Page;
constructor(page: Page) {
this._page = page;
export default class Wdi5PageBuilder extends Wdi5IPageBuilder {
pageName: string;
hashPath: string;
constructor(page: Page, pageName: string, hashPath: string) {
super(page); // -> this._page
this.pageName = pageName;
this.hashPath = hashPath;
}

generate(): string {
const p = new StringBuilder();
p.add(`const { wdi5 } = require("wdio-ui5-service");`).addNewLine();
p.add(`module.exports = class ${this.pageName} {`).addNewLine();

p.add(this._generateOpenMethod());

p.addTab().add(`async journey() {`).addNewLine();
this._generateJourneySteps().forEach((stepCode) => {
p.add(stepCode);
p.addNewLine();
});
p.addTab().add(`}`).addNewLine();

p.add(`};`);
return p.toString();
}

_generateJourneySteps(): string[] {
const stepCodes = this._page.steps.map((step) => {
return new Wdi5CodeStrategy().generateStepCode(step, 2);
});
return stepCodes;
}

_generateOpenMethod(): string {
const p = new StringBuilder();
p.addTab().add(`async open("${this.hashPath}") {`).addNewLine();
p.addTab(2).add(`wdi5.goTo("${this.hashPath}")`).addNewLine();
p.addTab().add(`}`).addNewLine();
return p.toString();
}
public abstract generate(): string;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,30 +1,95 @@
import { Step } from 'src/app/classes/Step';
import { InputStep, Step } from 'src/app/classes/Step';
import StringBuilder from 'src/app/classes/StringBuilder';

export default class Wdi5SingleStepStrategy {
public static generateSinglePressStep(step: Step): string {
public static generateSinglePressStep(
step: Step,
indent: number = 0
): string {
const pressStep = new StringBuilder();
pressStep.add(Wdi5SingleStepStrategy.generateSingleExistsStep(step));
pressStep.add(
Wdi5SingleStepStrategy.generateSingleExistsStep(step, indent)
);
pressStep.add('.press();');
return pressStep.toString();
}

public static generateSingleExistsStep(step: Step): string {
public static generateSingleInputStep(
step: InputStep,
indent: number = 0
): string {
const pressStep = new StringBuilder();
pressStep.add(
Wdi5SingleStepStrategy.generateSingleExistsStep(step, indent)
);
pressStep.add(`.enterText("${step.getResultText()}");`); // FIXME: add actual step input value
return pressStep.toString();
}

public static generateSingleExistsStep(
step: Step,
indent: number = 0
): string {
const exists = new StringBuilder();

indent > 0 ? exists.addTab(indent) : null;
exists.add('await browser.asControl({').addNewLine();
exists.addTab().add('selector: {').addNewLine();
exists.addBuilder(Wdi5SingleStepStrategy.generateSelector(step));
exists.addTab().add('}').addNewLine();

exists
.addTab(indent + 1)
.add('selector: {')
.addNewLine();

exists.addBuilder(
Wdi5SingleStepStrategy.generateSelector(step, indent + 2)
);

exists
.addTab(indent + 1)
.add('}')
.addNewLine();

indent > 0 ? exists.addTab(indent) : null;
exists.add('})');

return exists.toString();
}

private static generateSelector(step: Step): StringBuilder {
private static generateSelector(
step: Step,
indent: number = 0
): StringBuilder {
const selectorBuilder = new StringBuilder();
Object.values(step.recordReplaySelector).forEach((v) => {
selectorBuilder.addTab(2).add(`${v[0]}: ${v[1]}`).add(',').addNewLine();
});
selectorBuilder.remove(2).addNewLine();
/**
* traverse a json object and pretty-js-code format it as a string
* @param entry key-value pair that might either be string:string or string:object
*/
const traverse = (entry: [any, any]) => {
const prop = typeof entry[1];
if (prop === 'string' || prop === 'boolean') {
const isBool = prop === 'boolean' ? true : false;
const propValue =
prop === 'string' ? entry[1].replace('\\', '') : entry[1]; // neverending story of json escaping
selectorBuilder
.addTab(indent)
.add(`${entry[0]}: ${isBool ? propValue : '"' + propValue + '"'}`) // insert properly formatted value
.add(',')
.addNewLine();
} else {
selectorBuilder.addTab(indent).add(`${entry[0]}: {`).addNewLine();
for (const deepEntry of Object.entries(entry[1])) {
++indent;
traverse(deepEntry);
--indent;
}
selectorBuilder.addTab(indent).add('}').addNewLine();
}
};

// format the json-style record replay seclector as pretty looking js code
for (const entry of Object.entries(step.recordReplaySelector)) {
traverse(entry);
}
return selectorBuilder;
}
}
Empty file.
Loading

0 comments on commit 51c83ea

Please sign in to comment.