@skyramp/skyramp 1.2.22 → 1.2.24
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/package.json
CHANGED
|
@@ -267,4 +267,12 @@ export declare class SkyrampClient {
|
|
|
267
267
|
scenario: AsyncScenario | AsyncScenario[],
|
|
268
268
|
options?: SendScenarioOptions
|
|
269
269
|
): Promise<AsyncTestStatus>;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Fetches OTP from Skyramp email
|
|
273
|
+
*/
|
|
274
|
+
fetchLatestOtp(
|
|
275
|
+
email: string,
|
|
276
|
+
timestamp: Date
|
|
277
|
+
): string;
|
|
270
278
|
}
|
|
@@ -3,6 +3,7 @@ const koffi = require('koffi');
|
|
|
3
3
|
const TrafficConfig = require('./TrafficConfig');
|
|
4
4
|
const RequestV2 = require('./RequestV2');
|
|
5
5
|
const ResponseV2 = require('./ResponseV2');
|
|
6
|
+
const utils = require('../utils');
|
|
6
7
|
|
|
7
8
|
const workerInfoType = koffi.struct({
|
|
8
9
|
container_name: 'char*',
|
|
@@ -99,6 +100,7 @@ class SkyrampClient {
|
|
|
99
100
|
*/
|
|
100
101
|
constructor(kubeconfigPathOrOptions, clusterName, context, userToken, directory = process.cwd()) {
|
|
101
102
|
this.local_image = false;
|
|
103
|
+
this.timestamp = Date.now();
|
|
102
104
|
if (typeof kubeconfigPathOrOptions === 'object') {
|
|
103
105
|
const options = kubeconfigPathOrOptions;
|
|
104
106
|
this.workerNamespaces = [];
|
|
@@ -966,6 +968,41 @@ class SkyrampClient {
|
|
|
966
968
|
);
|
|
967
969
|
});
|
|
968
970
|
}
|
|
971
|
+
|
|
972
|
+
async fetchLatestOtp(email, timestamp) {
|
|
973
|
+
var latestGetResponse
|
|
974
|
+
// if timestamp is not given,
|
|
975
|
+
// we use the time that this client was instantiated
|
|
976
|
+
if (timestamp == undefined || timestamp == null) {
|
|
977
|
+
timestamp = this.timestamp;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (!email.includes("@otp.skyramp.dev")) {
|
|
981
|
+
throw new Error("non-Skyramp email is not supported")
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const username = email.replace("@otp.skyramp.dev", "")
|
|
985
|
+
|
|
986
|
+
for (let i = 0; i < 10; i ++) {
|
|
987
|
+
// Execute Request
|
|
988
|
+
latestGetResponse = await this.sendRequest({
|
|
989
|
+
url: "https://tokenize.skyramp.dev",
|
|
990
|
+
path: `/msg/${username}/latest`,
|
|
991
|
+
method: "GET"
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
if (latestGetResponse.statusCode != 200) {
|
|
995
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
996
|
+
continue
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
let codeTime = new Date(utils.getValue(latestGetResponse.responseBody, "timestamp"));
|
|
1000
|
+
if (codeTime > timestamp) {
|
|
1001
|
+
return utils.getValue(latestGetResponse.responseBody, "otp")
|
|
1002
|
+
}
|
|
1003
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
969
1006
|
}
|
|
970
1007
|
|
|
971
1008
|
module.exports = SkyrampClient;
|
|
@@ -143,8 +143,6 @@ async function retryWithLLM(skyrampLocator, error) {
|
|
|
143
143
|
let pageTitle = await skyrampLocator.page().title();
|
|
144
144
|
let pageUrl = skyrampLocator.page().url();
|
|
145
145
|
|
|
146
|
-
debug("retry with LLM", skyrampLocator._locator);
|
|
147
|
-
|
|
148
146
|
let newMsg = `${errorMessage} (while using selector: ${locatorStr})`;
|
|
149
147
|
|
|
150
148
|
let pageContent = await skyrampLocator.page().content();
|
|
@@ -175,7 +173,6 @@ async function retryWithLLM(skyrampLocator, error) {
|
|
|
175
173
|
debug(suggestions)
|
|
176
174
|
|
|
177
175
|
for (let suggestion of suggestions) {
|
|
178
|
-
debug(`try ${suggestion.selector} instead of ${skyrampLocator._locator}`);
|
|
179
176
|
try {
|
|
180
177
|
const newLocator = skyrampLocator.createLocatorFromString(suggestion.selector);
|
|
181
178
|
if (newLocator == null) {
|
|
@@ -184,7 +181,7 @@ async function retryWithLLM(skyrampLocator, error) {
|
|
|
184
181
|
|
|
185
182
|
const locatorCount = await newLocator.count();
|
|
186
183
|
|
|
187
|
-
debug(`
|
|
184
|
+
debug(`trying new Locator ${newLocator} instead of ${skyrampLocator._locator}, count = ${locatorCount}`);
|
|
188
185
|
|
|
189
186
|
if (locatorCount == 1) {
|
|
190
187
|
const func = newLocator[skyrampLocator.execFname];
|
|
@@ -197,7 +194,8 @@ async function retryWithLLM(skyrampLocator, error) {
|
|
|
197
194
|
skyrampLocator._skyrampPage.addLLMChoices(skyrampLocator._locator, newLocator, error.stack);
|
|
198
195
|
return result;
|
|
199
196
|
}).catch(error => {
|
|
200
|
-
|
|
197
|
+
// if it fails, move to the next one
|
|
198
|
+
debug(`retrying with LLM failed at ${skyrampLocator._locator} replacing ${newLocator}`, error.name);
|
|
201
199
|
});
|
|
202
200
|
}
|
|
203
201
|
} catch {
|
|
@@ -205,7 +203,9 @@ async function retryWithLLM(skyrampLocator, error) {
|
|
|
205
203
|
}
|
|
206
204
|
}
|
|
207
205
|
|
|
208
|
-
|
|
206
|
+
error.message = `Failed to find a good alternative for ${skyrampLocator._locator} with LLM.\n` +
|
|
207
|
+
`Please add "data-testid" attribute for a more stable locator\n` + error.message;
|
|
208
|
+
throw error;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
class SkyrampPlaywrightLocator {
|
|
@@ -246,7 +246,7 @@ class SkyrampPlaywrightLocator {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
async execute() {
|
|
249
|
-
debug(`execute ${ this._locator}.${this.execFname} ${this.execParam ?? ''} ${this.execArgs ?? ''}`)
|
|
249
|
+
debug(` execute ${ this._locator}.${this.execFname} ${this.execParam ?? ''} ${this.execArgs ?? ''}`)
|
|
250
250
|
const func = this._locator[this.execFname];
|
|
251
251
|
return func.call(this._locator, this.execParam, this.execArgs);
|
|
252
252
|
}
|
|
@@ -268,69 +268,111 @@ class SkyrampPlaywrightLocator {
|
|
|
268
268
|
return ret;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
wrapError(msg, error) {
|
|
272
|
+
let newMsg = msg;
|
|
273
|
+
if (this._skyrampPage.hasLLMChoices()) {
|
|
274
|
+
newMsg += this.generateLLMErrors();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
error.message = error.message + "\n" + newMsg
|
|
278
|
+
|
|
279
|
+
return error
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
hydrationErrorMsg = "Potentially a hydration issue. Please add enough waitForTimeout()"
|
|
283
|
+
|
|
284
|
+
newPrevHyrdationErrorMsg() {
|
|
285
|
+
return `Cannot find locator ${this._locator} and likely a hydration issue on ${this._previousLocator._locator}.\n` +
|
|
286
|
+
`Please add enough waitForTimeout() on ${this._previoousLocator._locator}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
newMultiLocatorErrorMsg() {
|
|
290
|
+
return `${this._locator} found ${this.locatorCount} locators. Please add "data-testid" attribute for a more stable locator`
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async _retryWithLLM(error, msg1) {
|
|
294
|
+
// if API_KEY is not defined, throw an error here without trying
|
|
295
|
+
if (!process.env.API_KEY) {
|
|
296
|
+
error.message = msg1 + error.message;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
debug(` try to get suggessions from LLM for ${this._locator}`);
|
|
301
|
+
return retryWithLLM(this, error).then(result => {
|
|
302
|
+
this.LLMselector = true;
|
|
303
|
+
return result;
|
|
304
|
+
}).catch(newError => {
|
|
305
|
+
throw this.wrapError("", newError)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
272
309
|
async SmartRetryWithFallback(fname, param, ...args) {
|
|
273
310
|
this.execFname = fname;
|
|
274
311
|
this.execParam = param;
|
|
275
312
|
this.execArgs = args;
|
|
276
313
|
|
|
277
314
|
let locatorCount = await this._locator.count();
|
|
278
|
-
|
|
315
|
+
let currentUrl = this._skyrampPage._page.url();
|
|
316
|
+
debug(`handling ${ this._locator }.${ fname }, count = ${ locatorCount }, ${currentUrl}`);
|
|
279
317
|
this.locatorCount = locatorCount
|
|
280
318
|
|
|
281
|
-
|
|
282
|
-
debug(`current url = ${currentUrl}`);
|
|
319
|
+
//debug(`current url = ${currentUrl}`);
|
|
283
320
|
|
|
284
321
|
// if locator exists, DOM is available
|
|
285
322
|
if (locatorCount == 1) {
|
|
323
|
+
debug(` single locator for ${this._locator} identified, ${currentUrl}`);
|
|
286
324
|
// we try action, this will most likely succeed
|
|
287
325
|
// if it fails, there could be potentially a hydration issue we can retry after a little wait time
|
|
288
|
-
|
|
289
|
-
|
|
326
|
+
try {
|
|
327
|
+
return await this.execute().then(result => {
|
|
328
|
+
return this._skyrampPage.checkNavigation(currentUrl, result);
|
|
329
|
+
});
|
|
330
|
+
} catch (error) {
|
|
331
|
+
debug(` first attempt of ${this._locator} failed, ${error.name}`);
|
|
290
332
|
if (error.name == "TimeoutError") {
|
|
291
|
-
|
|
292
|
-
|
|
333
|
+
// this is likely a hydration issue
|
|
334
|
+
// we wait a bit and retry
|
|
335
|
+
debug(` locator ${this._locator} exists, but execution failed, wait a bit and try from last step retry`);
|
|
336
|
+
await this.wait(defaultWaitForTimeout);
|
|
337
|
+
|
|
338
|
+
// Is this really necessary?
|
|
339
|
+
await this.execute().catch(() => {
|
|
340
|
+
debug(` failed second time and execute previous locator ${this._previousLocator._locator} again`);
|
|
341
|
+
return this._previousLocator.execute();
|
|
342
|
+
}).catch(() => {
|
|
343
|
+
debug(` failed to execute previous locator ${this._previousLocator._locator} again, continue`);
|
|
344
|
+
});
|
|
293
345
|
|
|
294
346
|
return this.execute().catch(newError => {
|
|
295
|
-
debug(`second attempt on ${this._locator} failed`)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
throw newError;
|
|
300
|
-
} else {
|
|
301
|
-
throw new Error("Potentially a hydration issue. Please add enough waitForTimeout()", error);
|
|
347
|
+
debug(` second attempt on ${this._locator} failed ${newError.name}`);
|
|
348
|
+
if (newError.name == "TimeoutError") {
|
|
349
|
+
// this hadn't happened yet. we need to validate if this is indeed hydration case
|
|
350
|
+
throw this.wrapError(this.hydrationErrorMsg, newError);
|
|
302
351
|
}
|
|
352
|
+
if (newError.message.includes("strict mode violation")) {
|
|
353
|
+
return this._retryWithLLM(newError, this.newMultiLocatorErrorMsg());
|
|
354
|
+
}
|
|
355
|
+
throw error;
|
|
303
356
|
});
|
|
304
357
|
}
|
|
358
|
+
if (error.message.includes("strict mode violation")) {
|
|
359
|
+
return this._retryWithLLM(error, this.newMultiLocatorErrorMsg());
|
|
360
|
+
}
|
|
361
|
+
// we do not handle the rest of the error cases, but simply forward error
|
|
305
362
|
throw error;
|
|
306
|
-
}
|
|
363
|
+
}
|
|
307
364
|
} else if (locatorCount > 0) {
|
|
308
|
-
|
|
309
|
-
debug(`multiple ${locatorCount} locators identified`)
|
|
310
|
-
|
|
365
|
+
debug(` multiple ${locatorCount} locators for ${this._locator} identified, ${currentUrl}`);
|
|
311
366
|
if (locatorCount > 5) {
|
|
367
|
+
// we fail even without trying
|
|
312
368
|
throw new Error(`${locatorCount} locators detected for ${this._locator}. Please add "data-testid" attribute for a more stable locator`);
|
|
313
369
|
}
|
|
314
370
|
|
|
371
|
+
// this will likely fail, but we try to get error message generated by playwright
|
|
315
372
|
return this.execute().then(result => {
|
|
316
|
-
return result;
|
|
373
|
+
return this._skyrampPage.checkNavigation(currentUrl, result);
|
|
317
374
|
}).catch(error => {
|
|
318
|
-
|
|
319
|
-
throw new Error(`${locatorCount} locators detected for ${this._locator}. Please add "data-testid" attribute for a more stable locator`, error)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
console.log(`${locatorCount} locators found for ${this._locator}`)
|
|
323
|
-
// this is the palce where we consult with LLM
|
|
324
|
-
return retryWithLLM(this, error).then(result => {
|
|
325
|
-
this.multiLocatorLLM = true;
|
|
326
|
-
return result;
|
|
327
|
-
}).catch(newError => {
|
|
328
|
-
if (this._skyrampPage.hasLLMChoices()) {
|
|
329
|
-
newError.message += this.generateLLMErrors();
|
|
330
|
-
throw newError;
|
|
331
|
-
}
|
|
332
|
-
throw new Error(`failed to find a good alternative for ${this._locator}. Please add "data-testid" attribute for a more stable locator`, newError);
|
|
333
|
-
});
|
|
375
|
+
return this._retryWithLLM(error, this.newMultiLocatorErrorMsg());
|
|
334
376
|
});
|
|
335
377
|
} else {
|
|
336
378
|
// if locator does not exist, we need to consider two cases
|
|
@@ -338,54 +380,42 @@ class SkyrampPlaywrightLocator {
|
|
|
338
380
|
// second, if locator id is not correct
|
|
339
381
|
// check if last step has potential hydration
|
|
340
382
|
if (this._previousLocator && this._previousLocator.locatorCount == 0) {
|
|
341
|
-
debug(`previous action ${this._previousLocator._locator} is potentially associated with hydration`);
|
|
383
|
+
debug(` previous action ${this._previousLocator._locator} is potentially associated with hydration, ${currentUrl}`);
|
|
342
384
|
// wait for a short time to finish hydration
|
|
343
385
|
await this.wait(defaultWaitForTimeout);
|
|
344
|
-
|
|
345
386
|
const previousCount = await this._previousLocator.count();
|
|
346
|
-
debug(`
|
|
347
|
-
|
|
387
|
+
debug(` re-execute the previous one ${this._previousLocator._locator}, new locator count = ${previousCount}, ${this._skyrampPage._page.url()}`);
|
|
348
388
|
// re-execute the previous locator
|
|
349
389
|
await this._previousLocator.execute().catch(() => {
|
|
350
390
|
// log the failure but continues to the current one
|
|
351
|
-
debug(`failed to execute previous locator ${this._previousLocator._locator} again`);
|
|
391
|
+
debug(` failed to execute previous locator ${this._previousLocator._locator} again, continue`);
|
|
352
392
|
});
|
|
353
393
|
|
|
354
394
|
try {
|
|
355
395
|
// then execute the current one
|
|
356
|
-
return await this.execute()
|
|
396
|
+
return await this.execute().then(result => {
|
|
397
|
+
return this._skyrampPage.checkNavigation(currentUrl, result);
|
|
398
|
+
});
|
|
357
399
|
} catch (error) {
|
|
358
400
|
if (error.name == "TimeoutError") {
|
|
359
|
-
debug(
|
|
401
|
+
debug(` ${this._locator} failed at first try. attempting again with some timeout`);
|
|
360
402
|
// wait for some time and re execute
|
|
361
403
|
await this.wait(defaultWaitForTimeout);
|
|
362
|
-
|
|
363
404
|
return this.execute().catch(newError => {
|
|
364
|
-
|
|
365
|
-
throw new Error(`Cannot find locator ${this._locator} and likely a hydration issue on ${this._previousLocator._locator}. Please add enough waitForTimeout()`, newError);
|
|
366
|
-
}
|
|
367
|
-
// retry with llm
|
|
368
|
-
return retryWithLLM(this, newError).then(result => {
|
|
369
|
-
this.singleLocatorLLM = true;
|
|
370
|
-
return result;
|
|
371
|
-
}).catch(newError2 => {
|
|
372
|
-
if (this._skyrampPage.hasLLMChoices()) {
|
|
373
|
-
newError2.message += this.generateLLMErrors();
|
|
374
|
-
throw newError2;
|
|
375
|
-
}
|
|
376
|
-
throw new Error(`Failed to find a good alternative of ${this._locator}. Please add "data-testid" attribute for a more stable locator`, newError2);
|
|
377
|
-
});
|
|
405
|
+
return this._retryWithLLM(newError, this.newPrevHydrationErrorMsg());
|
|
378
406
|
});
|
|
379
407
|
}
|
|
408
|
+
if (error.message.includes("strict mode violation")) {
|
|
409
|
+
debug(` a rare case when multiple locators are detected on ${this._locator}`);
|
|
410
|
+
return this._retryWithLLM(error, this.newMultiLocatorErrorMsg());
|
|
411
|
+
}
|
|
380
412
|
throw error;
|
|
381
413
|
}
|
|
382
414
|
} else {
|
|
383
|
-
if (this._previousLocator && this._previousLocator.
|
|
384
|
-
debug(
|
|
385
|
-
} else if (this._previousLocator && this._previousLocator.singleLocatorLLM) {
|
|
386
|
-
debug(`${this._locator} locator count is zero, but previous locator was selected by LLM from zero candidate`);
|
|
415
|
+
if (this._previousLocator && this._previousLocator.LLMselector) {
|
|
416
|
+
debug(` ${this._locator} locator count is zero, but previous locator was selected by LLM`);
|
|
387
417
|
} else {
|
|
388
|
-
debug(
|
|
418
|
+
debug(` ${this._locator} locator count is zero, but previous locator seems not related to hydration`);
|
|
389
419
|
}
|
|
390
420
|
// previous action may not be associated with hydration
|
|
391
421
|
// then we just try current locator. could be a locator with a wrong id
|
|
@@ -393,36 +423,30 @@ class SkyrampPlaywrightLocator {
|
|
|
393
423
|
await this.wait(defaultWaitForTimeout);
|
|
394
424
|
|
|
395
425
|
this.locatorCount = await this._locator.count();
|
|
396
|
-
debug(`
|
|
426
|
+
debug(` after waiting locator ${this._locator} count = ${this.locatorCount}, ${this._skyrampPage._page.url()}`);
|
|
397
427
|
|
|
398
428
|
try {
|
|
399
|
-
return await this.execute()
|
|
429
|
+
return await this.execute().then(result => {
|
|
430
|
+
return this._skyrampPage.checkNavigation(currentUrl, result);
|
|
431
|
+
});
|
|
400
432
|
} catch (error) {
|
|
401
433
|
if (error.name == "TimeoutError") {
|
|
402
434
|
debug(`${this._locator} failed at first try. attempting again with some timeout`);
|
|
403
435
|
await this.wait(defaultWaitForTimeout);
|
|
404
|
-
|
|
405
436
|
return this.execute().catch(newError => {
|
|
406
437
|
if (newError.name == "TimeoutError") {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return retryWithLLM(this, newError).then(result => {
|
|
412
|
-
this.singleLocatorLLM = true;
|
|
413
|
-
return result;
|
|
414
|
-
}).catch(newError2 => {
|
|
415
|
-
if (this._skyrampPage.hasLLMChoices()) {
|
|
416
|
-
newError2.message += this.generateLLMErrors();
|
|
417
|
-
throw newError2;
|
|
418
|
-
}
|
|
419
|
-
throw new Error(`Failed to find a good alternative of ${this._locator}. Please add "data-testid" attribute for a more stable locator`, newError2);
|
|
420
|
-
});
|
|
421
|
-
} else {
|
|
422
|
-
throw newError;
|
|
438
|
+
return this._retryWithLLM(newError, this.hydrationErrorMsg);
|
|
439
|
+
}
|
|
440
|
+
if (newError.message.includes("strict mode violation")) {
|
|
441
|
+
return this._retryWithLLM(newError, this.newMultiLocatorErrorMsg());
|
|
423
442
|
}
|
|
443
|
+
throw newError;
|
|
424
444
|
});
|
|
425
445
|
}
|
|
446
|
+
// if multiple locator found
|
|
447
|
+
if (this.locatorCount > 1 || error.message.includes("strict mode violation")) {
|
|
448
|
+
return this._retryWithLLM(error, this.newMultiLocatorErrorMsg());
|
|
449
|
+
}
|
|
426
450
|
throw error;
|
|
427
451
|
}
|
|
428
452
|
}
|
|
@@ -613,7 +637,7 @@ class SkyrampPlaywrightLocator {
|
|
|
613
637
|
}
|
|
614
638
|
|
|
615
639
|
async wait(t) {
|
|
616
|
-
debug(`wait for ${t}`);
|
|
640
|
+
debug(` wait for ${t}`);
|
|
617
641
|
return this._skyrampPage._page.waitForTimeout(t);
|
|
618
642
|
}
|
|
619
643
|
}
|
|
@@ -760,6 +784,15 @@ class SkyrampPlaywrightPage {
|
|
|
760
784
|
}
|
|
761
785
|
return true;
|
|
762
786
|
}
|
|
787
|
+
|
|
788
|
+
async checkNavigation(original, result) {
|
|
789
|
+
const newURL = this._page.url()
|
|
790
|
+
if (newURL != original) {
|
|
791
|
+
debug(`page navigation to ${newURL} detected, wait a bit`);
|
|
792
|
+
await this._page.waitForTimeout(1500);
|
|
793
|
+
}
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
763
796
|
}
|
|
764
797
|
|
|
765
798
|
function newSkyrampPlaywrightPage(page) {
|