Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@lu-development/psa
Advanced tools
The PSA module is a wrapper for the protractor functions which make them more flexible and dependable. Using it, you should no longer need to use browser.sleep()
or browser.wait()
in your tests.
PSA can be added to your repo by installing the package through npm and adding the following line to any file that uses psa:
// install with npm
npm install @lu-development/psa
// import into your test file
const psae = require(``'@lu-development/psa'``), psa = new psae()
Once it has been imported with this line, it can be called like any other module. psa's syntax is:
psa.expect(ELEMENT).ACTION()
// or
psa.element(ELEMENT).ACTION()
// examples:
psa.element(homePage.Profile()).click() // a protractor element can used as the ELEMENT
psa.element('#submitBtn').click() // or a css selector can be used as the ELEMENT
psa.element('#passInput').sendKeys('Str0ngP@ss')
psa.expect('#userId').toBePresent() // psa.expect() can be used instead of a jasmine expect() call
// equivalent protractor & jasmine syntax:
homePage.Profile().click()
$('#submitBtn').click()
$('#passInput').sendKeys('Str0ngP@ss')
expect($('#userId').isPresent()).toBeTruthy()
// shorthand psa.element() syntax example:
psa.e('#passInput').sendKeys('Str0ngP@ss')
Whats new? I will update the documentation after the new year so I have time to get in some more specs ;)
If the syntax is longer, why use the PSA wrapper? - PSA retries each action until it is completed or times out, this means that browser.sleep() and browser.wait()
do not need to be used at any point in tests! The need for waiting and sleeping is all taken care of in the PSA functions, so the tester does not have to worry about them.
Below is an example of how PSA removes the need for sleeps:
it('Enter information for US or international students', async () => {
if (user.country == "United States") {
await claimFormPage.SSN().sendKeys(user.ssn)
await browser.sleep(500)
await claimFormPage.zip().sendKeys(user.zip)
await browser.sleep(500)
} else {
await claimFormPage.intCheckBox().click()
await browser.sleep(500)
await claimFormPage.country().sendKeys(user.country)
await browser.sleep(500)
let phoneNum = user.phones.length > 1 ? user.phones[1] : user.phones[0]
await claimFormPage.phone().sendKeys(phoneNum)
await browser.sleep(500)
}
})
it('Enter information for US or international students', async () => {
if (user.country == "United States") {
await psa.element(claimFormPage.SSN()).sendKeys(user.ssn)
await psa.element(claimFormPage.zip()).sendKeys(user.zip)
} else {
await psa.element(claimFormPage.intCheckBox()).click()
await psa.element(claimFormPage.country()).sendKeys(user.country)
let phoneNum = user.phones.length > 1 ? user.phones[1] : user.phones[0]
await psa.element(claimFormPage.phone()).sendKeys(phoneNum)
}
})
Cleaner Test Code: browser.sleep()
and brower.wait()
statements do not take up any lines of code.
Quicker Test Execution Time: Only the time needed to perform the action down to the tenth of a second is taken. Generally when sleeps are called, they waste some amount of time.
More Robust Test Executions: If a page takes 2 seconds longer to load this run, the test won't fail. Since actions are retried, the tests work with inconsistent browser loading times.
Better Control Flow Statements: The expectResolution
flag can be used in any PSA function, as discussed below.
Easier to Write Tests: The programmer does not need to worry about the timing of their browser actions.
Less Time Needed to Write Tests: Since the programmer does not need to spend time thinking about the timing of browser actions, the total time it takes to automate a test is reduced.
A new tool to learn: It is fairly simple, but the team would have to spend time learning what it does.
Longer Syntax for Each Browser Action: psa.element()
or psa.e()
has to be added on the front of every line that calls a browser action.
Under the hood, PSA simply works like this: Note: this is not actually the PSA module code, but is logically accurate to what it does
// tries to click an element for 5 seconds
psa.click = async () => {
for (let var i = 0; i < 50; i++) {
try {
await this.protractorElement.click()
break
} catch {
await browser.sleep(100)
}
}
if (i == 50) {
throw 'Error: psa.click() failed - could not find element within 5 seconds'
} else {
return true
}
}
It tries to click on an element every tenth of a second for 5 seconds. Once it clicks on the element, it will break out of the loop and return true. If it is not able to click on the element for 5 seconds, it will throw an error.
Each PSA function has a sec
and a expectResolution
argument. The sec
argument should be a number (whole or decimal) and it determines how many seconds it takes for the action to time out. The expectResolution argument should be a boolean value and it determines if the call should expect resolution of the action. If the action is expected to be resolved, then PSA will throw an error if it times out (as determined by sec
). If the action is not expected to be resolved, then PSA will return false
if it times out instead of throwing an error. This is useful for control flow statements within test cases, like the following examples.
This code checks if there is an error message displayed on the page, and if there is, it enters the password again:
// if signing in failed, enter the password again and continue
if(await psa.e(msSignIn.passwordError()).isDisplayed(sec=1, expectResolution=false)) {
await msSignIn.passwordInput().clear()
await psa.element(msSignIn.passwordInput()).sendKeys(password)
await psa.element(msSignIn.signInButton()).click()
}
When logging into Azure with a mylu guest account, often times a prompt would appear asking if the user wanted to stay logged in, which would break the automation. Since it didn't appear every time, it could not be added to the automation like everything else. This was easy to handle using PSA:
// if ms asks prompts the user to stay signed in, click no and continue
if(await psa.e(msSignIn.doNotStaySignedInButton()).isPresent(1, false)) {
await psa.element(msSignIn.doNotStaySignedInButton()).click()
}
// Checks if the element is present until `sec` seconds pass or the element is found to be present
isPresent(sec, expectResolution)
// Checks if the element is not present until `sec` seconds pass or the element is not found to be present
isNotPresent(sec, expectResolution)
// Checks if the element is displayed until `sec` seconds pass or the element is found to be displayed
// First checks if the element is present
isDisplayed(sec, expectResolution)
// Checks if the element is displayed until `sec` seconds pass or the element is found not to be displayed
// First checks if the element is present
isNotDisplayed(sec, expectResolution)
// Checks if the element is enabled until `sec` seconds pass or the element is found to be enabled
// First checks if the element is present
isEnabled(sec, expectResolution)
// Checks if the element is enabled until `sec` seconds pass or the element is found not to be enabled
// First checks if the element is present
isNotEnabled(sec, expectResolution)
// Tries to click an element until `sec` seconds pass or it is clicked
// First checks if the element is present, displayed, and enabled
click(sec, expectResolution)
// Tries to click an element linking to another page until `sec` seconds pass or the element clicked on is no longer present
// First checks if the element is present, displayed, and enabled
// Note: The clickLink() function is faster and more reliable than the click() function, and should be used over the click() function in all places applicable
clickLink(sec, expectResolution)
// Tries to send `key` keys to the element until `sec` seconds pass or the keys are sent
// First checks if the element is present, displayed, and enabled
sendKeys(key, sec, expectResolution)
// Tries to match an element's text to `textToMatch` until `sec` seconds pass or the text matches
matchText(textToMatch, sec, expectResolution)
// Checks if an element's text contains `textToContain` until `sec` seconds pass or the element text includes `textToContain`
includesText(textToContain, sec, expectResolution)
// Tries to match an element's `attribute` attribute to `value` until `sec` seconds pass or the element attribute equals `value`
matchAttribute(attribute, value, sec, expectResolution)
// Checks if the current page url contains `textToContain` until `sec` seconds pass or the url contains `textToContain`
urlContains(textToContain, sec, expectResolution)
// This function does not need to be called after calling the element function. This is the proper way to call it:`
psa.urlContains('liberty.edu')
Using PSA functions for control flow can be tricky at times, so make sure you consider these four unique situations as you are writing specs.
// Good when expecting an error msg to appear
// Logic: As soon as an error msg appears within one second, log 'Error is displayed'
if (psa.e('#errorMsg').isDisplayed(1, false)) {
console.log('Error is displayed')
}
// Good when expecting an error msg to disappear
// Logic: As as soon as an error msg is not displayed within one second, log 'Error is not displayed'
if (psa.e('#errorMsg').isNotDisplayed(1, false)) {
console.log('Error is not displayed')
}
// Good when expecting an error msg to not appear
// Logic: If an error msg does not appear within one second, log 'Error is not displayed'
if (!(psa.e('#errorMsg').isDisplayed(1, false))) {
console.log('Error is not displayed')
}
// Good when expecting an error msg to not disappear
// Logic: If an error msg does not disappear within one second, log 'Error is displayed'
if (!(psa.e('#errorMsg').isNotDisplayed(1, false))) {
console.log('Error is displayed')
}
These four control flow statements apply to all psa functions that check if an element is something.
With the PSA file in your project, psaSlowRun
and psaDebuggingStatements
will become global variables to help with writing automation.
You can set this variable to true
or false
either at the top of the utils.psa.js file or anywhere in your it blocks. Sometimes, PSA functions click and inspect elements too fast for a user to follow on screen. If psaSlowRun
is set to true, a half a second pause will be put in between each action, so it gives the user enough time to watch what the automation is actually doing and can verify it is doing the correct things. This is most useful if you target a specific set of commands in an it block and set it equal to true before those commands, and false after those commands. Here is an example if it's usage:
describe('Navigate to the validate user form and submit correct user data', () => {
it('The User Validation Page should load', async () => {
psaSlowRun = true
await browser.get(this.validateUserLink)
await psa.element(vp.logo).isDisplayed()
})
validateSnippet.enterInfo(data.missingRiskVlidateUser)
validateSnippet.submitForm()
validateSnippet.checkCodePage()
it('Save the user validation code', async () => {
this.userValidationCode = await $(vp.codePageCode).getText()
psaSlowRun = false
})
})
Only the code that is run between when psaSlowRun
was set to true
and when it was set back to false
will run slowly, allowing the tester to more easily follow what is happening on screen.
You can set this variable to true
or false
either at the top of the utils.psa.js file or anywhere in your it blocks. Sometimes, it's hard to tell exactly what function call is doing. If psaDebuggingStatements
is set to true
, PSA will log every function call, value check of an element / attempt to perform an action on it, and every resolution of a function call. This is most useful if you target a specific set of commands in an it block and set it equal to true before those commands, and false after those commands.
describe('Navigate to the validate user form and submit correct user data', () => {
it('The User Validation Page should load', async () => {
await browser.get(this.validateUserLink)
psaDebuggingStatements = true
await psa.element(vp.logo).isDisplayed()
psaDebuggingStatements = false
})
validateSnippet.enterInfo(data.missingRiskVlidateUser)
validateSnippet.submitForm()
validateSnippet.checkCodePage()
it('Save the user validation code', async () => {
this.userValidationCode = await $(vp.codePageCode).getText()
})
})
This will output very verbose messages to the console about what is happening every tenth of a second as PSA checks the vp.logo
element's display value.
Since PSA throws an error when an action is not resolved (when expectResolution
is set to false
- as is by default) and true
when it is resolved, these two lines are functionally equal within an 'it' block:
// functionally equal:
expect(psa.e(page.button()).isPresent()).toBeTruthy()
psa.e(page.button()).isPresent()
Because of this behavior of PSA, jasmine expect
statements are not needed around PSA function calls.
The sec
argument is not actually the number of seconds that psa will retry something, sec
is the number of iterations / 10
that psa will try something, with a tenth of a second pause in between iterations. So sec = 5
is 50 iterations with a tenth of a second of a pause in between, which ends up taking roughly 5 seconds. Knowing this, you can use the sec argument better, for instance if you want to check if something is present only ONCE and not again, the following logic can be used:
// check if an element is present exactly once, since total iterations = 0.1 * 10 = 1
await psa.element(page.header).isPresent(0.1)
// check if an element is present exactly twice, since total iterations = 0.2 * 10 = 2
await psa.element(page.header).isPresent(0.2)
I highly encourage anyone using PSA to read through the source code to get a good understanding of how exactly PSA works under the hood. If you see any room for improvement for PSA, please create a new branch then create a Pull Request for your changes! They will be greatly appreciated. PSA was designed in a specific way to allow for easy additions to it! Protractor has many many functions which are not all wrapped by PSA, so if you need to use a protractor function that is not already wrapped by a PSA function, then please add to the project! The template for wrapping PSA around any protractor element function is like so (placeholders that should be changed are shown in all caps):
FUNCNAME = async (FUNCARG, sec = 5, expectResolution = true) => {
psaReporter(`FUNCNAME() call: Checking if element ${this.protractorElementSelector} DESC OF FUNC PURPOSE`)
ANY PREREQUISITE CONDITIONS
return this.retryLoop(sec, expectResolution, FUNCARG, async (sec, expectedValue) => {
let result = await this.protractorElement.PROTRACTORFUNC()
return {
'value': result,
'boolValue': BOOLEAN EXPRESSION REPRESENTING THE PROTRACTOR FUNCTION CALL PASS CRITEREA == expectedValue,
'progressMsg': `MESSAGE TO DISPLAY AS PSA TRIES THE PROTRACTOR FUNCTION CALL`,
'trueMsg': `Element ${this.protractorElementSelector} DESC OF WHY IT PASSED`,
'falseMsg': `Element ${this.protractorElementSelector} DESC OF WHY IT FAILED`,
'errorMsg': `psa.matchText() failed - element ${this.protractorElementSelector} DESC OF WHY IT FAILED within ${sec} seconds.`
}
})
}
Here is an example of what a psa function looks like when it is complete. Note that the third argument in the retryLoop
function call is passed into the expectedValue
argument of the arrow function being passed as the fourth argument to the retryLoop
call:
// checks if an element's text matches the textToMatch within sec seconds
matchText = async (textToMatch, sec = 5, expectResolution = true) => {
psaReporter(`matchText() call: Checking if element ${this.protractorElementSelector} text matches ${textToMatch}`)
// make sure the element is present before getting the text
await this.isPresent(sec, expectResolution)
return this.retryLoop(sec, expectResolution, textToMatch, async (sec, expectedValue) => {
let getTextResult = await this.protractorElement.getText()
return {
'value': getTextResult,
'boolValue': getTextResult == expectedValue,
'progressMsg': `Comparing '${getTextResult}' to '${expectedValue}'`,
'trueMsg': `Element ${this.protractorElementSelector} text matches '${expectedValue}'!`,
'falseMsg': `Element ${this.protractorElementSelector} text does not match '${expectedValue}'!`,
'errorMsg': `psa.matchText() failed - element ${this.protractorElementSelector} text '${getTextResult}' did not match '${expectedValue}' within ${sec} seconds.`
}
})
}
Here is an example of a psa function that does not need to take in any arguments:
// checks if the element is present within sec seconds
isPresent = async (sec = 5, expectResolution = true) => {
psaReporter(`isPresent() call: Checking if element ${this.protractorElementSelector} is present`)
return await this.retryLoop(sec, expectResolution, true, async (sec, expectedValue) => {
let isPresentResult = await this.protractorElement.isPresent()
return {
'value': isPresentResult,
'boolValue': isPresentResult == expectedValue,
'progressMsg': `Checking if element ${this.protractorElementSelector} is present`,
'trueMsg': `Element ${this.protractorElementSelector} is present!`,
'falseMsg': `Element ${this.protractorElementSelector} is not present!`,
'errorMsg': `psa.isPresent() failed - could not find element ${this.protractorElementSelector} within ${sec} seconds.`
}
})
}
And finally here is an example of a psa function that takes two arguments:
// checks if an element's attribute equals value within sec seconds
matchAttribute = async (attribute, value, sec = 5, expectResolution = true) => {
psaReporter(`matchAttribute() call: Checking if element ${this.protractorElementSelector} ${attribute} attribute equals ${value}`)
// make sure the element is present before getting it's text
await this.isPresent(sec, expectResolution)
return await this.retryLoop(sec, expectResolution, {'attribute':attribute, 'value': value}, async (sec, expectedValue) => {
let getAttributeResult = await this.protractorElement.getAttribute(expectedValue['attribute'])
return {
'value': getAttributeResult,
'boolValue': getAttributeResult == expectedValue['value'],
'progressMsg': `Comparing '${getAttributeResult}' to equal '${expectedValue['value']}'`,
'trueMsg': `Element ${this.protractorElementSelector} attribute equals '${expectedValue['value']}'!`,
'falseMsg': `Element ${this.protractorElementSelector} attribute does not equals '${expectedValue['value']}'!`,
'errorMsg': `psa.matchAtteibute() failed - element ${this.protractorElementSelector} attribute '${getAttributeResult}' did not equal '${expectedValue['value']}' within ${sec} seconds.`
}
})
}
If you have any comments or questions about PSA, please do not hesitate to reach out to John Strunk.
FAQs
A JS wrapper for protractor
The npm package @lu-development/psa receives a total of 0 weekly downloads. As such, @lu-development/psa popularity was classified as not popular.
We found that @lu-development/psa demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 12 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.