@auxcode/auxta 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # AuxTA
2
+
3
+ # Content
4
+ * [puppeteer](#puppeteer)
5
+ * [install](#install)
6
+ * [setup](#setup)
7
+ * [running](#running)
8
+
9
+ ## Puppeteer
10
+
11
+ https://pptr.dev
12
+
13
+ https://github.com/puppeteer/puppeteer
14
+
15
+ ## Install
16
+
17
+ npm i
18
+
19
+ ## setup
20
+
21
+ ```
22
+ {
23
+ # All background functions that will be run, listed
24
+ "suitesList": [
25
+ "{filename}-background"
26
+ ],
27
+ # The tested site's url
28
+ "baseURL": "https://{sitename}/",
29
+ # The url where the tests are ran
30
+ "testsURL": "https://{sitename}/",
31
+ # The organization's name
32
+ "organization": "string",
33
+ # The digital product's name
34
+ "digitalProduct": "bedrijfsfinanciering.com",
35
+ # Max timeout in milliseconds
36
+ # Default 1000
37
+ "timeout": 1000,
38
+ # The credentials of the user that uploads the tests to the site
39
+ "email": "",
40
+ "password": "",
41
+ "token": ""
42
+ }
43
+
44
+ ```
45
+
46
+ ## running
47
+
48
+ * Create a main function that calls the run() function which starts all tests
49
+ * Create a function for each test that calls the `auxtaPuppeteer.run(event, callback, featureName, scenarioName)` function
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@auxcode/auxta",
3
+ "version": "0.1.3",
4
+ "description": "",
5
+ "main": "src/AuxTA.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "test": "npm run build & node ./build/AuxTA.js"
9
+ },
10
+ "author": "auxcode",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "axios": "^0.24.0",
14
+ "chrome-aws-lambda": "^10.1.0",
15
+ "dotenv": "^10.0.0",
16
+ "chromium": "^3.0.3",
17
+ "puppeteer-core": "^10.0.0",
18
+ "typescript": "^4.5.2"
19
+ },
20
+ "devDependencies": {
21
+ "puppeteer": "^10.0.0",
22
+ "@babel/preset-typescript": "^7.16.0"
23
+ }
24
+ }
package/src/AuxTA.ts ADDED
@@ -0,0 +1,60 @@
1
+ import puppeteer, { Puppeteer } from "./puppeteer/puppeteer";
2
+ import { FunctionHelper } from "./macros/helpers/code.helper";
3
+ import f from "./macros/helpers/code.helper";
4
+ import run from "./auxta/run";
5
+ import { Log } from "./auxta/services/log.service";
6
+ import defaultLog from "./auxta/services/log.service";
7
+ import path from "path";
8
+ import fs from "fs";
9
+ import { StepStatusEnum } from "./auxta/enums/step-status.enum";
10
+ import { UploadModel } from "./auxta/models/upload.model";
11
+ import * as dotenv from "dotenv";
12
+ import { setupConfig } from "./auxta/configs/config";
13
+
14
+ dotenv.config();
15
+
16
+ class AuxTA {
17
+ public puppeteer: Puppeteer = puppeteer;
18
+ public func: FunctionHelper = f;
19
+ public run: Function = run;
20
+ public logger: Log.LogSteps = defaultLog;
21
+
22
+ private readonly uploadModel: UploadModel;
23
+
24
+ constructor() {
25
+ let file;
26
+ try {
27
+ //todo find a better way to load the file
28
+ file = path.join(__dirname, "../../../../../../", "suitesList.json");
29
+ const jsonConfig = JSON.parse(fs.readFileSync(file).toString());
30
+ try {
31
+ setupConfig(jsonConfig)
32
+ } catch (e) {
33
+ if (e instanceof Error) console.log("Missing field in suitesList.json:", e.message)
34
+ process.exit(1);
35
+ }
36
+ this.uploadModel = new UploadModel(jsonConfig.organization, jsonConfig.baseURL, jsonConfig.digitalProduct);
37
+ } catch (e) {
38
+ console.log("Missing or corrupted config: suitesList.json. Searching in location:", file)
39
+ console.log(e);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ public getUploadModel(): UploadModel {
45
+ return this.uploadModel;
46
+ }
47
+ }
48
+
49
+ export const StepStatus = StepStatusEnum;
50
+
51
+ const auxta: AuxTA = new AuxTA();
52
+
53
+ export const auxtaLogger = auxta.logger;
54
+ export const auxtaPuppeteer = auxta.puppeteer;
55
+ export const auxtaSuggest = auxta.logger.addSuggestion;
56
+ export const auxtaLog = auxta.logger.push;
57
+ export const auxtaRun = auxta.run;
58
+ export const auxtaFunc = auxta.func;
59
+
60
+ export default auxta;
@@ -0,0 +1,35 @@
1
+ export function setupConfig(jsonConfig: any) {
2
+ if (!jsonConfig.baseURL) throw new Error("baseURL");
3
+ config.baseURL = jsonConfig.baseURL;
4
+ if (!jsonConfig.digitalProduct) throw new Error("digitalProduct");
5
+ config.digitalProduct = jsonConfig.digitalProduct;
6
+ if (!jsonConfig.testsURL) throw new Error("testsURL");
7
+ config.netlifyPath = (process.env.ENVIRONMENT == 'LOCAL') ? 'http://localhost:9999/' : jsonConfig.testsURL;
8
+ if (!jsonConfig.organization) throw new Error("organization");
9
+ config.organization = jsonConfig.organization;
10
+ if (!jsonConfig.token) throw new Error("token");
11
+ config.token = jsonConfig.token;
12
+ if (!jsonConfig.email) throw new Error("email");
13
+ config.auxtaCredentials.email = jsonConfig.email;
14
+ if (!jsonConfig.password) throw new Error("password");
15
+ config.auxtaCredentials.password = jsonConfig.password;
16
+ if (!jsonConfig.suitesList) throw new Error("suitesList");
17
+ config.suitesList = jsonConfig.suitesList;
18
+ if (jsonConfig.timeout) config.timeout = jsonConfig.timeout;
19
+ }
20
+
21
+ export let config = {
22
+ auxtaURL: "https://auxta.live/.netlify/functions/",
23
+ baseURL: "",
24
+ siteURL: "",
25
+ organization: "",
26
+ netlifyPath: "",
27
+ digitalProduct: "",
28
+ token: "",
29
+ timeout: 1000,
30
+ suitesList: [],
31
+ auxtaCredentials: {
32
+ email: "",
33
+ password: "",
34
+ },
35
+ }
@@ -0,0 +1,6 @@
1
+ export enum StepStatusEnum {
2
+ PASSED = 'passed',
3
+ FAILED = 'failed',
4
+ SKIPPED = 'skipped',
5
+ SUGGESTION = 'suggestion',
6
+ }
@@ -0,0 +1,11 @@
1
+ export function returnImage(code: number, screenshot: Buffer) {
2
+ return {
3
+ isBase64Encoded: true,
4
+ statusCode: code,
5
+ headers: {
6
+ "Content-Type": "image/png",
7
+ "Content-Length": screenshot.length.toString()
8
+ },
9
+ body: screenshot.toString("base64")
10
+ };
11
+ }
@@ -0,0 +1,14 @@
1
+ import { join } from 'path';
2
+
3
+ (async () => {
4
+ const r = /^--params\.suite=(.*)$/g;
5
+ let suite = process.argv.find(el => !!el.match(r));
6
+ if (!suite) throw `Locale is undefined`;
7
+ let suites = suite.replace('--params.suite=', '');
8
+ let suitesArr = suites.split(',');
9
+ if (!suitesArr) suitesArr = [ suites ];
10
+ for (const suite of suitesArr) {
11
+ let f = require(join(process.cwd(), 'build', 'functions', 'controllers', suite));
12
+ await f.handler();
13
+ }
14
+ })();
@@ -0,0 +1,61 @@
1
+ import { basename } from 'path'
2
+ import log from "../services/log.service";
3
+ import {
4
+ Feature,
5
+ postNotifications,
6
+ Steps,
7
+ updateReport,
8
+ uploadFeature,
9
+ uploadScenario
10
+ } from '../services/report.service';
11
+ import { startSuite } from '../utilities/start-suite.helper';
12
+ import { StepStatusEnum } from "../enums/step-status.enum";
13
+ import UploadModel from "../models/upload.model";
14
+
15
+ export async function AfterEach(body: any, featureName: string, scenarioName: string, statusCode: number, screenshotBuffer: Buffer, errMessage?: string) {
16
+ let isFinal: boolean = false;
17
+ if (process.env.ENVIRONMENT == "LIVE") {
18
+ console.log('asc ' + errMessage);
19
+ console.log('--- Uploading scenario and feature for AUXTA report ---');
20
+ const stepsArr = log.returnScenarioReport();
21
+ try {
22
+ const scenarioRes = await uploadScenario(stepsArr, scenarioName, screenshotBuffer, errMessage);
23
+ const featureRes = await uploadFeature(scenarioRes, featureName, basename(__filename), (statusCode != 200));
24
+ const feature: Feature = {
25
+ featureId: featureRes.featureRef,
26
+ featureName: featureRes.name,
27
+ featureUri: featureRes.uri,
28
+ status: featureRes.status,
29
+ scenariosCount: featureRes.scenariosCount
30
+ }
31
+ const stepCounts: Steps = {
32
+ failedSteps: log.getStatusCount(StepStatusEnum.FAILED),
33
+ passedSteps: log.getStatusCount(StepStatusEnum.PASSED),
34
+ skippedSteps: log.getStatusCount(StepStatusEnum.SKIPPED),
35
+ suggestedSteps: log.getStatusCount(StepStatusEnum.SUGGESTION)
36
+ }
37
+ isFinal = (!(body.nextSuites && body.nextSuites.length > 0));
38
+ await updateReport(body.reportId, feature, stepCounts, isFinal);
39
+ console.log('--- Finished uploading report data to AUXTA ---');
40
+ } catch (error) {
41
+ console.log('--- Failed to update report in AUXTA ---')
42
+ console.log(error)
43
+ }
44
+ }
45
+ if (isFinal) await AfterComplete(body.reportId, body.endEmail, body);
46
+ else if (body && body.nextSuites && body.nextSuites.length > 0)
47
+ try {
48
+ await startSuite(body.nextSuites, body.reportId);
49
+ } catch {
50
+ }
51
+ }
52
+
53
+ export async function AfterComplete(reportId: string, endEmail: string, body: UploadModel) {
54
+ if (process.env.ENVIRONMENT == "LIVE") {
55
+ await postNotifications(reportId, body);
56
+ }
57
+ }
58
+
59
+ export function sleep(ms: number) {
60
+ return new Promise(resolve => setTimeout(resolve, ms));
61
+ }
@@ -0,0 +1,52 @@
1
+
2
+ export class UploadModel {
3
+ private _reportId: string;
4
+ private _nextSuites: [];
5
+ private readonly _digitalProduct: string;
6
+ private readonly _baseUrl: string;
7
+ private readonly _organization: string;
8
+ private readonly _environment: string;
9
+
10
+ constructor(org: string, baseUrl: string, digitalProduct: string) {
11
+ this._reportId = '';
12
+ this._nextSuites = [];
13
+ this._organization = org;
14
+ this._digitalProduct = digitalProduct;
15
+ this._environment = process.env.NODE_ENV || "";
16
+ this._baseUrl = baseUrl;
17
+ }
18
+
19
+ get reportId(): string {
20
+ return this._reportId;
21
+ }
22
+
23
+ set reportId(value: string) {
24
+ this._reportId = value;
25
+ }
26
+
27
+ get nextSuites(): [] {
28
+ return this._nextSuites;
29
+ }
30
+
31
+ set nextSuites(value: []) {
32
+ this._nextSuites = value;
33
+ }
34
+
35
+ get digitalProduct(): string {
36
+ return this._digitalProduct;
37
+ }
38
+
39
+ get baseUrl(): string {
40
+ return this._baseUrl;
41
+ }
42
+
43
+ get organization(): string {
44
+ return this._organization;
45
+ }
46
+
47
+ get environment(): string {
48
+ return this._environment;
49
+ }
50
+ }
51
+
52
+ export default UploadModel;
@@ -0,0 +1,14 @@
1
+ import { createEmptyReport } from "./services/report.service";
2
+ import { startSuite } from "./utilities/start-suite.helper";
3
+ import auxta from "../AuxTA";
4
+ import { config } from "./configs/config";
5
+
6
+ export default async function run(event: any) {
7
+ const model = auxta.getUploadModel();
8
+ let token: string = event.queryStringParameters.token;
9
+ let reportId: string | undefined = event.queryStringParameters.reportId;
10
+ if (token !== config.token) return {statusCode: 401, message: 'Unauthorized'}
11
+ const suites = config.suitesList.slice(0);
12
+ await startSuite(suites, reportId || await createEmptyReport(model));
13
+ return {statusCode: 204}
14
+ }
@@ -0,0 +1,46 @@
1
+ import { StepStatusEnum } from "../enums/step-status.enum";
2
+
3
+ export namespace Log {
4
+ export interface Step {
5
+ keyword: string,
6
+ name: string,
7
+ result: {
8
+ status: StepStatusEnum,
9
+ duration: number
10
+ }
11
+ }
12
+
13
+ export class LogSteps {
14
+ private stepLog: Log.Step[] = [];
15
+ private statusCounter = {
16
+ [StepStatusEnum.PASSED]: 0,
17
+ [StepStatusEnum.FAILED]: 0,
18
+ [StepStatusEnum.SKIPPED]: 0,
19
+ [StepStatusEnum.SUGGESTION]: 0,
20
+ }
21
+
22
+ public addSuggestion(text: string) {
23
+ this.push('Suggest', text, StepStatusEnum.SUGGESTION, 1000000);
24
+ }
25
+
26
+ public clear() {
27
+ this.stepLog = [];
28
+ }
29
+
30
+ public push(keyword: string, name: string, status: StepStatusEnum, duration: number) {
31
+ console.log(`System log -- status: ${status} -- : ${name} `);
32
+ this.statusCounter[status] = this.statusCounter[status] + 1;
33
+ this.stepLog.push({ keyword: keyword, name: name, result: { status: status, duration: duration } });
34
+ }
35
+
36
+ public returnScenarioReport() {
37
+ return this.stepLog;
38
+ }
39
+
40
+ public getStatusCount(status: StepStatusEnum): number{
41
+ return this.statusCounter[status];
42
+ }
43
+ }
44
+ }
45
+
46
+ export default new Log.LogSteps();
@@ -0,0 +1,174 @@
1
+ import axios from "axios";
2
+ import { Log } from "./log.service";
3
+ import { config } from "../configs/config";
4
+ import UploadModel from "../models/upload.model";
5
+
6
+ export interface Feature {
7
+ featureId: string,
8
+ featureName: string,
9
+ featureUri: string,
10
+ status: string,
11
+ scenariosCount: number
12
+ }
13
+
14
+ export interface Scenario {
15
+ featureId: string,
16
+ featureName: string,
17
+ featureUri: string,
18
+ status: string,
19
+ scenariosCount: number
20
+ }
21
+
22
+ export interface Steps {
23
+ failedSteps: number,
24
+ passedSteps: number,
25
+ skippedSteps: number,
26
+ suggestedSteps: number
27
+ }
28
+
29
+ function headers(token: string) {
30
+ return {
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ 'cookie': `authToken=${token}`
34
+ }
35
+ }
36
+ }
37
+
38
+ async function auth() {
39
+ //todo what happens if credentials are invalid?
40
+ return (await axios.post(
41
+ config.auxtaURL + 'login',
42
+ config.auxtaCredentials,
43
+ {
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ }
47
+ }
48
+ )).data.token;
49
+ }
50
+
51
+ export async function createEmptyReport(body: any): Promise<string> {
52
+ const token = await auth();
53
+ return (await axios.post(
54
+ config.auxtaURL + "create-empty-report",
55
+ {
56
+ environment: body.environment,
57
+ digitalProductName: body.digitalProduct,
58
+ start: new Date(),
59
+ url: body.baseUrl
60
+ },
61
+ headers(token)
62
+ )).data.reportId;
63
+ }
64
+
65
+ export async function uploadScenario(stepLog: Log.Step[], scenarioName: string, screenshot?: Buffer, errMessage?: string) {
66
+ let scenarios = [];
67
+ let token = await auth();
68
+ let lastStep = stepLog[stepLog.length - 1];
69
+ const stepLogFormat = JSON.parse(JSON.stringify(stepLog));
70
+ stepLogFormat.pop();
71
+ let output = (await axios.post(
72
+ config.auxtaURL + "create-scenario",
73
+ {
74
+ json: {
75
+ name: scenarioName,
76
+ steps: [
77
+ ...stepLogFormat,
78
+ (lastStep!.result.status == 'failed')
79
+ ? Object.assign(lastStep, {
80
+ result: {
81
+ ...lastStep!.result,
82
+ error_message: JSON.stringify(errMessage)
83
+ }
84
+ })
85
+ : lastStep,
86
+ {
87
+ keyword: "After",
88
+ result: {
89
+ status: "passed",
90
+ duration: 2678000000
91
+ },
92
+ embeddings: (screenshot) ? [{
93
+ data: screenshot.toString("base64"),
94
+ mime_type: "image/png"
95
+ }] : undefined,
96
+ }
97
+ ],
98
+ }
99
+ },
100
+ headers(token)
101
+ )).data.scenarios;
102
+ scenarios.push({
103
+ scenarioRef: output.scenarioRef,
104
+ name: output.name,
105
+ status: output.status,
106
+ stepsCount: output.stepsCount
107
+ });
108
+ return scenarios;
109
+ }
110
+
111
+ export async function uploadFeature(scenarioId: any[], scenarioName: string, uri: string, hasFailed: boolean) {
112
+ let token = await auth();
113
+ return (await axios.post(
114
+ config.auxtaURL + "create-feature",
115
+ {
116
+ json: {
117
+ name: scenarioName,
118
+ uri: uri
119
+ },
120
+ scenarios: scenarioId,
121
+ hasFailed: hasFailed
122
+ },
123
+ headers(token)
124
+ )).data;
125
+ }
126
+
127
+ export async function updateReport(reportId: string, feature: Feature, steps: Steps, isFinal: boolean) {
128
+ let token = await auth();
129
+ await axios.post(
130
+ config.auxtaURL + "update-inprogress-report",
131
+ {
132
+ reportId: reportId,
133
+ isFinal: isFinal,
134
+ data: {
135
+ feature: {
136
+ featureRef: feature.featureId,
137
+ name: feature.featureName,
138
+ uri: feature.featureUri,
139
+ status: feature.status,
140
+ scenariosCount: feature.scenariosCount
141
+ },
142
+ ...steps
143
+ }
144
+ },
145
+ headers(token)
146
+ );
147
+ }
148
+
149
+ export async function getReport(reportId: string): Promise<any> {
150
+ let token = await auth();
151
+ return (await axios.post(
152
+ config.auxtaURL + "get-report",
153
+ {reportId: reportId},
154
+ headers(token)
155
+ )).data;
156
+ }
157
+
158
+ export async function getLastDayResults(): Promise<any> {
159
+ let token = await auth();
160
+ return (await axios.get(
161
+ config.auxtaURL + `get-last-day-results?url=${config.baseURL}`,
162
+ headers(token)
163
+ )).data.results;
164
+ }
165
+
166
+ export async function postNotifications(reportId: string, body: UploadModel) {
167
+ let token = await auth();
168
+ await axios.post(`${config.auxtaURL}post-notification-after-run-background`, {
169
+ environmentName: body.environment,
170
+ digitalProductName: body.digitalProduct,
171
+ organizationName: body.organization,
172
+ reportId: reportId,
173
+ }, headers(token));
174
+ }
@@ -0,0 +1,51 @@
1
+ export interface RunTime {
2
+ duration: String,
3
+ startTime: Date,
4
+ endTime: Date,
5
+ }
6
+
7
+ export class Runtime {
8
+ private start: Date;
9
+
10
+ private end: Date;
11
+
12
+ private runtime!: RunTime;
13
+
14
+ constructor(s: Date, e: Date) {
15
+ this.start = s;
16
+ this.end = e;
17
+ }
18
+
19
+ private calculateDuration() {
20
+ let currentTime = new Date;
21
+ let duration = currentTime.getTime() - this.start.getTime();
22
+ return duration;
23
+ }
24
+
25
+ private splitAtDecimanlMinutes(time: number) {
26
+ let minutes = time.toFixed(2).toString().split('.');
27
+ return `${minutes[0]}m ${(Number(minutes[1]) / 100 * 60).toFixed(0)}s`;
28
+ }
29
+
30
+ private splitAtDecimanlHours(time: number) {
31
+ let hours = time.toFixed(2).toString().split('.');
32
+ let minutes = (Number(hours[1]) / 100 * 60).toFixed(2).toString().split('.');
33
+ return `${hours[0]}h ${minutes[0]}m ${(Number(minutes[1]) / 100 * 60).toFixed(0)}s`;
34
+ }
35
+
36
+ private formatRuntime(duration: number) {
37
+ if(duration / 1000 < 60) return `${(duration / 1000).toFixed(0)}s`
38
+ else {
39
+ if(duration / 60000 < 60) return this.splitAtDecimanlMinutes(duration / 60000);
40
+ else return this.splitAtDecimanlHours(duration / 3600000);
41
+ }
42
+ }
43
+
44
+ public returnRuntime() : RunTime {
45
+ let duration = this.formatRuntime(this.calculateDuration());
46
+ let startTime = this.start;
47
+ let endTime = this.end;
48
+ this.runtime = { duration, startTime, endTime }
49
+ return this.runtime;
50
+ }
51
+ }
@@ -0,0 +1,29 @@
1
+ import { RunTime } from "../services/runtime.service";
2
+
3
+ export class CalculateRuntime {
4
+ private runtime: RunTime;
5
+ public startTimeSeconds: string;
6
+ public startTimeMinutes: string;
7
+ public startTimeHours: string;
8
+ public startTimeDay: string;
9
+ public startTimeMonth: string;
10
+ public endTimeSeconds: string;
11
+ public endTimeMinutes: string;
12
+ public endTimeHours: string;
13
+ public endTimeDay: string;
14
+ public endTimeMonth: string;
15
+ constructor(r: RunTime){
16
+ this.runtime = r;
17
+ this.startTimeSeconds = (this.runtime.startTime.getSeconds() < 10) ? `0${this.runtime.startTime.getSeconds()}` : `${this.runtime.startTime.getSeconds()}`
18
+ this.startTimeMinutes = (this.runtime.startTime.getMinutes() < 10) ? `0${this.runtime.startTime.getMinutes()}` : `${this.runtime.startTime.getMinutes()}`;
19
+ this.startTimeHours = (this.runtime.startTime.getHours() < 10) ? `0${this.runtime.startTime.getHours()}` : `${this.runtime.startTime.getHours()}`;
20
+ this.startTimeDay = (this.runtime.startTime.getDate() < 10) ? `0${this.runtime.startTime.getDate()}` : `${this.runtime.startTime.getDate()}`;
21
+ this.startTimeMonth = (this.runtime.startTime.getMonth() + 1 < 10) ? `0${this.runtime.startTime.getMonth() + 1}` : `${this.runtime.startTime.getMonth() + 1}`;
22
+
23
+ this.endTimeSeconds = (this.runtime.endTime.getSeconds() < 10) ? `0${this.runtime.endTime.getSeconds()}` : `${this.runtime.endTime.getSeconds()}`;
24
+ this.endTimeMinutes = (this.runtime.endTime.getMinutes() < 10) ? `0${this.runtime.endTime.getMinutes()}` : `${this.runtime.endTime.getMinutes()}`;
25
+ this.endTimeHours = (this.runtime.endTime.getHours() < 10) ? `0${this.runtime.endTime.getHours()}` : `${this.runtime.endTime.getHours()}`;
26
+ this.endTimeDay = (this.runtime.endTime.getDate() < 10) ? `0${this.runtime.endTime.getDate()}` : `${this.runtime.endTime.getDate()}`;
27
+ this.endTimeMonth = (this.runtime.endTime.getMonth() + 1 < 10) ? `0${this.runtime.endTime.getMonth() + 1}` : `${this.runtime.endTime.getMonth() + 1}`;
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ import { auxtaPuppeteer } from "../../AuxTA";
2
+
3
+ export async function captureScreenshot() {
4
+ const pages = await auxtaPuppeteer.defaultPage.browser().pages();
5
+ const lastPage = pages[pages.length - 1];
6
+
7
+ const screenshotBuffer = await lastPage.screenshot({
8
+ fullPage: true,
9
+ encoding: 'binary'
10
+ }
11
+ );
12
+ if (Buffer.isBuffer(screenshotBuffer))
13
+ return screenshotBuffer;
14
+ else throw `Failed to capture buffer screenshot`;
15
+ }
@@ -0,0 +1,11 @@
1
+ import axios from 'axios';
2
+ import { config } from "../configs/config";
3
+
4
+ export function startSuite(suites: string[], reportId?: string) {
5
+ console.log('Starting next suite: ' + suites[0]);
6
+ console.log(suites,reportId);
7
+ return axios.post(
8
+ `${config.netlifyPath}.netlify/functions/${suites.shift()}`,
9
+ { nextSuites: suites, reportId: reportId}
10
+ );
11
+ }
@@ -0,0 +1,69 @@
1
+ import log from "../../auxta/services/log.service";
2
+ import puppeteer from "../../puppeteer/puppeteer";
3
+ import { StepStatusEnum } from "../../auxta/enums/step-status.enum";
4
+
5
+ export class FunctionHelper {
6
+ public readonly timeout: number = Number(process.env.AUXTA_TIMEOUT);
7
+
8
+ public async clickByText(selector: string, text: string, page = puppeteer.defaultPage) {
9
+ await page.waitForTimeout(this.timeout);
10
+ const splitedQuotes = text.replace(/'/g, `', "'", '`)
11
+ const escapedText = `concat('${splitedQuotes}', '')`;
12
+ const [linkHandlers] = await page.$x(`//${selector}[. = ${escapedText}]`);
13
+
14
+ if (linkHandlers) {
15
+ log.push('And', `I click ${selector} with text ${text}`, StepStatusEnum.PASSED, 1000000);
16
+ await linkHandlers.click();
17
+ } else {
18
+ log.push('And', `I click ${selector} with text ${text}`, StepStatusEnum.FAILED, 1000000);
19
+ throw new Error(`Link not found: ${text}`);
20
+ }
21
+ }
22
+
23
+ public async waitForSelectorWithText(selector: string, text: string) {
24
+ const splitedQuotes = text.replace(/'/g, `', "'", '`)
25
+ const escapedText = `concat('${splitedQuotes}', '')`;
26
+ const linkHandlers = await puppeteer.defaultPage.$x(`//${selector}[. = ${escapedText}]`);
27
+ if (linkHandlers.length > 0) {
28
+ log.push('And', `I checked for ${text} and found it.`, StepStatusEnum.PASSED, 1000000);
29
+ return true;
30
+ } else {
31
+ log.push('And', `I checked for ${text} and found it.`, StepStatusEnum.FAILED, 1000000);
32
+ throw new Error(`Link not found: ${text}`);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Waiting for selector with timeout 60000
38
+ *
39
+ * @function waitForSelector
40
+ * @param {string} option - visible, hidden
41
+ * @param selector - class to select
42
+ * @param time
43
+ * @param page
44
+ */
45
+
46
+ public async waitForSelector(option: string, selector: string, time: number = this.timeout, page = puppeteer.defaultPage) {
47
+ await page.waitForSelector(selector, {
48
+ [option]: true,
49
+ timeout: time
50
+ });
51
+ }
52
+
53
+ public async click(className: string, page = puppeteer.defaultPage) {
54
+ await page.waitForTimeout(this.timeout);
55
+ await page.click(className);
56
+ }
57
+
58
+ public async urlContains(selector: string) {
59
+ const url = puppeteer.defaultPage.url();
60
+ if (!url.includes(selector)) {
61
+ await log.push('And', `I am on the ${selector} page`, StepStatusEnum.FAILED, 1000000);
62
+ throw new Error(`I am not at the ${selector} page`)
63
+ }
64
+ await log.push('And', `I am on the ${selector} page`, StepStatusEnum.PASSED, 1000000);
65
+ }
66
+
67
+ }
68
+
69
+ export default new FunctionHelper();
@@ -0,0 +1,96 @@
1
+ import chromium from 'chrome-aws-lambda';
2
+ import log from "../auxta/services/log.service";
3
+ import { returnImage } from '../auxta/helpers/response.helper';
4
+ import { captureScreenshot } from "../auxta/utilities/screenshot.helper";
5
+ import { AfterEach } from "../auxta/hooks/report.hook";
6
+ import auxta from "../AuxTA";
7
+ import { StepStatusEnum } from "../auxta/enums/step-status.enum";
8
+ import { UploadModel } from "../auxta/models/upload.model";
9
+ import puppeteer_core from 'puppeteer-core';
10
+
11
+ export class Puppeteer {
12
+ public defaultPage!: puppeteer_core.Page;
13
+ private browser!: puppeteer_core.Browser;
14
+
15
+ public async startBrowser() {
16
+ this.browser = await chromium.puppeteer.launch({
17
+ executablePath: process.env.NODE_ENV !== 'production' ? undefined : await chromium.executablePath,
18
+ args: [
19
+ '--start-maximized',
20
+ '--no-sandbox',
21
+ '--disable-setuid-sandbox',
22
+ '--disable-dev-shm-usage',
23
+ '--single-process'
24
+ ],
25
+ defaultViewport: chromium.defaultViewport,
26
+ // Return back to headless for commit
27
+ headless: process.env.ENVIRONMENT == 'LOCAL' ? false : chromium.headless
28
+ });
29
+ this.defaultPage = await this.browser.newPage();
30
+ await this.defaultPage.setViewport({
31
+ width: 1440,
32
+ height: 900
33
+ });
34
+ }
35
+
36
+ public async goto(page: string) {
37
+ await this.defaultPage.goto(page);
38
+ }
39
+
40
+ public async type(field: string, value: string) {
41
+ await this.defaultPage.type(field, value);
42
+ }
43
+
44
+ public async waitForNetwork() {
45
+ await this.defaultPage.waitForNetworkIdle();
46
+ }
47
+
48
+ public async close() {
49
+ if (this.browser) {
50
+ let pages = await this.browser.pages();
51
+ await Promise.all(pages.map((page: { close: () => any; }) => page.close()));
52
+ await this.browser.close();
53
+ }
54
+ }
55
+
56
+ public async run(event: any, callback: any, FeatureName = 'Test feature', ScenarioName = 'Test scenario', uploadModel?: UploadModel, close?: boolean) {
57
+ if (uploadModel === undefined) uploadModel = auxta.getUploadModel();
58
+ if (close === undefined) close = Puppeteer.setupHeader(event, uploadModel)
59
+ let screenshotBuffer: Buffer;
60
+ let errMessage: string | undefined;
61
+ let statusCode: number = 200;
62
+
63
+ try {
64
+ await log.push('When', `Starting puppeteer process`, StepStatusEnum.PASSED, 1000000);
65
+ await this.startBrowser()
66
+ await callback(event)
67
+ screenshotBuffer = await captureScreenshot();
68
+ await log.push('When', `Finished puppeteer process`, StepStatusEnum.PASSED, 1000000);
69
+ } catch (err: any) {
70
+ console.log(`This is an error ${err}`);
71
+ errMessage = err;
72
+ statusCode = 500;
73
+ screenshotBuffer = await captureScreenshot();
74
+ await log.push('When', `Finished puppeteer process`, StepStatusEnum.FAILED, 1000000);
75
+ }
76
+ if (close) await this.close();
77
+
78
+ await AfterEach(uploadModel, FeatureName, ScenarioName, statusCode, screenshotBuffer, errMessage);
79
+
80
+ return returnImage(statusCode, screenshotBuffer);
81
+ }
82
+
83
+ private static setupHeader(event: any, uploadModel: UploadModel) {
84
+ let close = true;
85
+ if (process.env.ENVIRONMENT !== 'LOCAL') {
86
+ const body = JSON.parse(event.body);
87
+ uploadModel.reportId = body.reportId;
88
+ uploadModel.nextSuites = body.nextSuites;
89
+ } else if (event.queryStringParameters.close) {
90
+ close = event.queryStringParameters.close === "true";
91
+ }
92
+ return close;
93
+ }
94
+ }
95
+
96
+ export default new Puppeteer();
package/tsconfig.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Basic Options */
4
+ "incremental": true, /* Enable incremental compilation */
5
+ "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
6
+ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
7
+ // "lib": [], /* Specify library files to be included in the compilation. */
8
+ // "allowJs": true, /* Allow javascript files to be compiled. */
9
+ // "checkJs": true, /* Report errors in .js files. */
10
+ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
11
+ // "declaration": true, /* Generates corresponding '.d.ts' file. */
12
+ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
13
+ "sourceMap": false, /* Generates corresponding '.map' file. */
14
+ // "outFile": "./", /* Concatenate and emit output to single file. */
15
+ "outDir": "./build", /* Redirect output structure to the directory. */
16
+ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17
+ // "composite": true, /* Enable project compilation */
18
+ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
19
+ "removeComments": true, /* Do not emit comments to output. */
20
+ // "noEmit": true, /* Do not emit outputs. */
21
+ // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22
+ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23
+ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24
+ /* Strict Type-Checking Options */
25
+ "strict": true, /* Enable all strict type-checking options. */
26
+ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
27
+ // "strictNullChecks": true, /* Enable strict null checks. */
28
+ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
29
+ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
30
+ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
31
+ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
32
+ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
33
+ /* Additional Checks */
34
+ "noUnusedLocals": true, /* Report errors on unused locals. */
35
+ "noUnusedParameters": false, /* Report errors on unused parameters. */
36
+ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
37
+ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
38
+ /* Module Resolution Options */
39
+ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
40
+ "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
41
+ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
42
+ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
43
+ "typeRoots": [ "./node_modules/@types" ], /* List of folders to include type definitions from. */
44
+ // "types": [], /* Type declaration files to be included in compilation. */
45
+ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
46
+ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
47
+ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
48
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
49
+ /* Source Map Options */
50
+ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
51
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
52
+ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
53
+ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
54
+ /* Experimental Options */
55
+ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
56
+ "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
57
+ /* Advanced Options */
58
+ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
59
+ "skipLibCheck": true,
60
+ "resolveJsonModule": true
61
+ },
62
+ "exclude": [
63
+ "node_modules", "build", "public", "content"
64
+ ]
65
+ }