testcafe await t.expect with timeout is flaky - testing

I have a scenario where I wait for element to not appear and once that happen I perform click action.
what actual occur is that the click takes place although the expect for an element with timeout option isn't validate
await t.expect(StatusOnProgressIcon.exists).notOk('Unexistence of On progress status icon', { timeout: 120000 })
await t.click(someTab)
I would expect that the second line will execute only after the first line is asserted positive

My attempts to replicate this behavior were unsuccessful. The Smart Wait Mechanism for Assertions correctly waits until the assertion's actual value matches the expected value:
Here's my sample test case.
61609192.html
<!DOCTYPE html>
<head>
</head>
<body>
<p id="icon">Status Icon</p>
<button id="button" type="button">Add to cart</button>
</body>
<script>
setTimeout(() => {
document.getElementById("icon").remove();
}, 8000);
</script>
</html>
61609192.js
import { Selector} from 'testcafe';
fixture('fixture')
.page('./61609192.html');
test('test', async t => {
const icon = Selector("#icon");
const button = Selector("button");
await t.expect(icon.exists).notOk('Icon does not exist', { timeout: 120000 })
await t.click(button)
});
Please update my test case to illustrate the issue or provide your own test case where we can see the issue in action.

Try
await t.expect(await StatusOnProgressIcon.exists).notOk('Unexistence of On progress status icon', { timeout: 120000 })

Related

TestCafe chrome:headless intermittently fails with Vue event emit

Chrome: Version 91.0.4472.114 (Official Build) (x86_64)
TestCafe: 1.14.2
Vue codes
Template
<input
id="amount"
v-model="amount"
min="0"
name="amount"
class="input-field"
:placeholder="labelPlaceholder"
#input="validateAmount"
>
Event emitter
validateAmount() {
...
this.$root.$emit('new-amount', this.id, this.valid, this.amount);
},
Event receiver
this.$root.$on('new-amount', (id, valid, amount) => {
if (this.id === id) {
this.amount = amount;
this.validAmount = valid; // In the screenshot below
}
});
Test codes
TestCafe with chrome:headless (browser mode works) intermittently fails for tests with Vue event emit. No matter test waits for 2 seconds or not, the result is same but when it enables Quarantine mode it passes (I don't want to use this though)
test('Should be enabled when a valid value is entered', async (t) => {
await t
.typeText(amount, '1')
.expect(amount.value)
.eql('1');
await t.wait(2000);
const after = button.withAttribute('disabled', 'disabled').exists;
await t.expect(after).notOk();
});
Result TestCafe test (screenshot on error)
1) AssertionError: expected true to be falsy
Result by user (MUST BE)
Since enabling the quarantine mode fixes the problem, I assume that the issue occurs because sometimes the event is not fired within 2 seconds.
Try to rewrite your code in the following way:
test('Should be enabled when a valid value is entered', async (t) => {
await t
.typeText(amount, '1')
.expect(amount.value).eql('1');
.expect(button.hasAttribute('disabled')).notOk();
});

testCafe returning Success on timeout

I have a page that is in break now, my backend is correcting it, but my test still returning success.
When I click to open the page, it is loading forever, doesn't open the page, and my "expect" was suppose to return an error, as it didn't find the "#btnStopService".
import 'testcafe';
import { Selector, ClientFunction } from 'testcafe';
fixture('Compass - Main page')
.page('http://localhost:3000/')
.beforeEach(async t => {
//login
t.ctx.username = 'admin';
t.ctx.password = 'admin';
await t.typeText(Selector('input').withAttribute('name','username'), t.ctx.username, {
paste: true,
replace: true,
});
await t.typeText(Selector('input').withAttribute('name','password'), t.ctx.password, {
paste: true,
replace: true,
});
await t.click(Selector('button').withAttribute('tabindex','0'));
})
.afterEach(async t => {
//logout
await t.click(Selector('#logoutBtn'));
});
test('Check if Services / Site Health page is loading... *** NOT WORKING ***', async t => {
await t.click(Selector('a').withExactText('Services'));
await t.click(Selector('a').withAttribute('href','#objectstore/sites/health'));
await t.expect(Selector('#btnStopService')).ok();
});
I am running it with:
testcafe edge .\test_spec.ts --selector-timeout 6000
The return I got is:
PS C:\ThinkOn\Compass_Test\Test1> testcafe edge .\test_spec.ts --selector-timeout 6000
Using locally installed version of TestCafe.
Running tests in:
- Microsoft Edge 17.17133 / Windows 10
Compass - Main page
√ Check if Services / Site Health page is loading... *** NOT WORKING ***
1 passed (23s)
PS C:\ThinkOn\Compass_Test\Test1>
Thanks all!!!
You need to add in an assertion option:
await t.expect(Selector('#btnStopService').exists).ok();

PDFTron full api is not getting loaded in WebViewer

I am trying to get the full api of PDFTron working from the WEbViewer. I followed the steps in the link below.
https://www.pdftron.com/documentation/web/guides/full-api/setup/
But I am getting the error given below in console while loading the webviewer.
Uncaught (in promise) Error: Full version of PDFNetJS has not been loaded. Please pass the "fullAPI: true" option in your WebViewer constructor to use the PDFNet APIs.
at Object.get (CoreControls.js:1694)
at z.docViewer.on ((index):43)
at CoreControls.js:398
at Array.forEach (<anonymous>)
at z.O (CoreControls.js:398)
at CoreControls.js:213
This is my code.
WebViewer({
path: 'WebViewer-6.0.2/lib', // path to the PDFTron 'lib' folder on your server
type: 'html5',
initialDoc: 'forms/local.pdf', // You can also use documents on your server
fullAPI: true,
}, document.getElementById('viewer'))
.then(instance => {
const docViewer = instance.docViewer;
const annotManager = instance.annotManager;
const Annotations = instance.Annotations;
Annotations.ChoiceWidgetAnnotation.FORCE_SELECT=true;
const Actions = instance.Actions;
docViewer.on('documentLoaded', async () => {
const PDFNet = instance.PDFNet;
await PDFNet.Initialize();
// This part requires the full API: https://www.pdftron.com/documentation/web/guides/full-api/setup/
alert('async');
const doc = docViewer.getDocument();
// Get document from worker
const pdfDoc = await doc.getPDFDoc();
pdfDoc.getAcroForm().putBool("NeedAppearances", true);
});
docViewer.on('documentLoaded', () => {
docViewer.on('annotationsLoaded', () => {
const annotations = annotManager.getAnnotationsList();
annotations.forEach(annot => {
console.log('fieldName => '+annot.fieldName);
});
});
Please help me resolve this.
EDIT
Modified the code as suggested by #Andy.
The updated code in index.html file looks like below,
<!DOCTYPE html>
<html>
<head>
<title>Basic WebViewer</title>
</head>
<!-- Import WebViewer as a script tag -->
<script src='WebViewer-6.0.2/lib/webviewer.min.js'></script>
<body>
<div id='viewer' style='width: 1024px; height: 600px; margin: 0 auto;'>
<script>
WebViewer({
path: 'WebViewer-6.0.2/lib', // path to the PDFTron 'lib' folder on your server
type: 'html5',
fullAPI: true,
// licenseKey: 'Insert commercial license key here after purchase',
}, document.getElementById('viewer'))
.then(async instance => {
const { Annotations, Tools, CoreControls, PDFNet, PartRetrievers, docViewer, annotManager } = instance;
await PDFNet.Initialize();
Annotations.ChoiceWidgetAnnotation.FORCE_SELECT=true;
const Actions = instance.Actions;
docViewer.on('documentLoaded', async () => {
// This part requires the full API: https://www.pdftron.com/documentation/web/guides/full-api/setup/
const doc = docViewer.getDocument();
// Get document from worker
const pdfDoc = await doc.getPDFDoc();
const acroFrom = await pdfDoc.getAcroForm();
acroform.putBool("NeedAppearances", true);
});
instance.loadDocument('forms/test.pdf');
});
</script>
</div>
</body>
</html>
I am loading the file from a http server in my project folder.
http-server -a localhost -p 7080
Unfortunately, I am getting the same error.
Error: Full version of PDFNetJS has not been loaded. Please pass the "fullAPI: true" option in your WebViewer constructor to use the PDFNet APIs.
We are currently evaluating PDFTron, so the licenseKey option is not passed in the WebViewer constructor.
Kindly help me on this.
I have tried out the code you have provided and was still not able to reproduce the issue you are encountering. I do typically perform the initialize outside WebViewer events so the initialization occurs only once:
WebViewer(...)
.then(instance => {
const { Annotations, Tools, CoreControls, PDFNet, PartRetrievers, docViewer } = instance;
const annotManager = docViewer.getAnnotationManager();
await PDFNet.initialize(); // Only needs to be initialized once
docViewer.on('documentLoaded', ...);
docViewer.on('annotationsLoaded', ...);
});
Also, I noticed that you attach an an event handler to annotationsLoaded every time documentLoaded is triggered. I am not sure if that is intentional or desirable but this can lead to the handler triggering multiple times (when switching documents).
This may not matter but instead of using initialDoc, you could try instance.loadDocument after the initialize instead.
await PDFNet.initialize();
docViewer.on('documentLoaded', ...);
docViewer.on('annotationsLoaded', ...);
instance.loadDocument('http://...');
There is one last thing to mention about the full API. The APIs will return a promise most of the time as the result so you will have to await the return value most of the time.
const acroFrom = await pdfDoc.getAcroForm();
// You can await this too. Especially if you need a reference to the new bool object that was
acroform.putBool("NeedAppearances", true);
Let me know if this helps!

Is there a way in testcafe to get the load time of specific page elements?

Suppose I navigate to a page and I want certain page elements to be displayed within a specific time duration. How can I get the time taken for specific page elements to be loaded?
I have tried using visibilityCheck option with selector timeout in the command-line argument.
I also tried exception with a timeout.
Neither of these is working as expected.
try {
await loginPage.signInToPortal()
await loginPage.login( 'xxxx','yyyy')
await Selector('div').withText('Something').with({ visibilityCheck: true });
} catch (e) {
logger.error("Testcase C111111 failed...")
throw(e)
}
OR
try {
await loginPage.signInToPortal()
await loginPage.login( 'xxxx','yyyy')
const appLabel = Selector('div').withText('Something').with({ visibilityCheck: true });
await t.expect(appLabel.innerText).contains('Something', { timeout: 5000 });
} catch (e) {
logger.error("Testcase C111111 failed...")
throw(e)
}
As far as I understand, you want to check that the element exists and has some inner text after some period of time. If the time passed, but the element does not exist or does not have some text, you want the test to fail.
Your approach is correct in general, but I think you do not need the visibilityCheck option here.
Since I do not know how exactly your project works, I created a sample. Let me show it and explain how timeouts work in TestCafe.
The page:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script>
setTimeout(() => {
var div = document.createElement('div');
document.body.appendChild(div);
setTimeout(() => {
div.innerHTML = 'test';
}, 6000);
}, 6000);
</script>
</body>
</html>
The test code:
import { Selector } from 'testcafe';
fixture `fixture`
.page `../pages/index.html`;
const selector = Selector('div', { timeout: 4000 });
test(`Recreate invisible element and click`, async t => {
await t.expect(selector.innerText).eql('test', 'error message', { timeout: 13000 });
});
Here, I have a div element, which appears only after 6s. During this period, the assertion checks whether the element exists. This code is executed: Selector('div', { timeout: 4000 });. Since 4s is less that 6s, the test fails because it cannot find an element during the timeout.
However, if I change the timeout to 7s, Selector('div', { timeout: 7000}); TestCafe finds the div and starts waiting until the div has correct inner text.
The assertion timeout is 13s now. 13s is greater that 6s (time required for an element to appear) + 6s (time required for an element to have a correct text), so assertion will succeed. However, if I change the assertion timeout from 13s to 11s, it will fail.
See also: Built-In Wait Mechanisms

Using MSAL v0.1.3 with Aurelia for Authentication

I've found all kind of examples of using MSAL and even examples of using MSAL with SPA applications (generally Angular or React) but I'm struggling to get this to work with Aurelia.
I did the following to ensure that the library is actually working as expected.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/0.1.3/js/msal.min.js"></script>
<style>
.hidden {
visibility: hidden;
}
</style>
</head>
<body>
<div id="username"></div>
<div id="token"></div>
<button id="login" onclick="loginPopup()">Log In</button>
<button id="logout" class="hidden">Log Out</button>
<script>
var authClient = new Msal.UserAgentApplication("my v2 endpoint client id",null);
function loginPopup(){
authClient.loginPopup(["openid","user.readbasic.all"])
.then(token => {
authClient.acquireTokenSilent(["user.readbasic.all"])
.then(accessToken => {
updateUI(token);
},
error => {
authClient.acquireTokenPopup(["user.readbasic.all"])
.then(accessToken => {
updateUI(token);
},
error => {
console.log("Token Error: " + error)
})
})
},
error =>{
console.log("Login error " + error)
})
}
function updateUI(token){
var loginbutton = document.getElementById("login");
loginbutton.className = "hidden";
let usernamediv = document.getElementById("username");
let tokendiv = document.getElementById("token");
usernamediv.innerText = authClient.getUser().name;
tokendiv.innerText = token;
}
</script>
</body>
</html>
That code works great. You click the Log In button, the Login Popup is displayed, you select the user, enter your password and the popup disappears and the UI is updated appropriately with username and token.
Now I'm trying to add this into my Aurelia app as follows:
main.js
export async function configure(aurelia){
aurelia.use
.standardConfiguration()
.developmentLogging();
let msClient = new Msal.UserAgentApplication('my v2 endpoint client id",null);
aurelia.use.instance("AuthService",msClient);
await aurelia.start();
if(msClient.getUser()) {
await aurelia.setRoot(PLATFORM.moduleName("app"));
else
await aurelia.setRoot(PLATFORM.moduleName("login/login"));
login.js
export class Login {
static inject = [Aurelia, "AuthService"]
constructor(aurelia, auth){
this.authService = auth
this.app = aurelia
}
activate(){
//Also tried this code in constructor
this.authService.loginPopup(["openid","user.readbasic.all"])
.then(token => {
this.app.setRoot(PLATFORM.moduleName("app"))
});
}
}
However, with this code in place, the app loads and navigates to the login page which pops up the login popup. However, once the user enters/selects name and password, the popup screen does not go away. The app (behind the popup) seems to reload and navigate to the app.js viewmodel but the popup remains on the screen and appears to be asking the user to enter/select username.
I've also tried using loginRedirect rather than loginPopup with a similar result in that the app is constantly redirected to the login page even after authenticating.
I'm guessing this has to do with the way MSAL is trying to respond to the app and the way Aurelia is trying to handle navigation, etc but I can't figure out where things are going awry.
Any help would be greatly appreciated!
Use the attached() lifecycle callback
It seems like everything is working, but the popup isn't going away. It's hard to know why without seeing the code in action, but the first thing you'll want to try is moving the relevant code to the attached() callback, which is called once the view and view-model are bound and added to the DOM.
Do NOT return the login promise in activate()
If you returned the login promise in activate(), activation wouldn't complete until the promise resolved. However, app.setRoot() is being called when the promise resolves as well, which means that you would be starting a new navigation before the first one completed. This is bad.
activate() {
// THIS WOULD BREAK
return this.authService.loginPopup(["openid","user.readbasic.all"])
.then(token => {
this.app.setRoot(PLATFORM.moduleName("app"))
});
}
Use the detatched() lifecycle callback
If that doesn't work, the next thing to do is check to see if authService.loginPopup() is properly logging in and resolving. If so, you can always go ahead and manually remove the popup from the DOM in the case that the library wasn't cleaning up after itself. You should do this detatched() lifecycle callback.