Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@lu-development/psa

Package Overview
Dependencies
Maintainers
9
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lu-development/psa

A JS wrapper for protractor

  • 2.1.1
  • npm
  • Socket score

Version published
Weekly downloads
0
Maintainers
9
Weekly downloads
 
Created
Source

Using the Protractor Smart Actions wrapper module:

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.

Importing PSA into your specs

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')

PSA 2.0.0 Is here!

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:

Protractor VS PSA

Test Spec Using Regular Protractor Functions:
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)
	}
})
Test Spec Using PSA:
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)
	}
})
The Pros of Using PSA:
  • 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.

The Cons of Using PSA:
  • 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.

More Details On How PSA works:

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()
}
The currently available PSA functions
// 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')
Control Flow Statement Notes:

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.

Other PSA Features

With the PSA file in your project, psaSlowRun and psaDebuggingStatements will become global variables to help with writing automation.

psaSlowRun

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.

psaDebuggingStatements

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.

Error Throwing

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.

Performing an action only n times

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)

Adding to PSA

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.`
		}
	})
}

Thanks for reading this far :)

If you have any comments or questions about PSA, please do not hesitate to reach out to John Strunk.

Keywords

FAQs

Package last updated on 06 Jan 2021

Did you know?

Socket

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.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc