Een uitgebreide gids voor het testen van authenticatieflows met Selenium en het Yenlo Connext Platform
Selenium is een populair open-source, geautomatiseerd testframework dat veel wordt gebruikt voor het testen van webapplicaties. Het stelt testers in staat om testcases te schrijven en uit te voeren in een breed scala aan programmeertalen, waaronder Java, Python, Ruby en C#. Een van de belangrijkste voordelen van Selenium is de mogelijkheid om echte gebruikersinteracties met een webapplicatie te simuleren, waardoor het een waardevolle tool is voor het testen van het authenticatieproces van webapplicaties.
Authenticatie is het proces waarbij de identiteit van een gebruiker wordt geverifieerd, wat op verschillende manieren kan worden gedaan, zoals via gebruikersnaam en wachtwoord, OAuth/OIDC en Single Sign-On (SSO). Met Selenium kunnen testers het in- en uitlogproces van webapplicaties automatiseren en de functionaliteit en veiligheid van het authenticatieproces testen. Het authenticatieproces is een cruciaal element van elke webapplicatie, omdat dit ervoor zorgt dat alleen geautoriseerde gebruikers toegang hebben tot gevoelige informatie en bronnen. Het testen van dit proces is een essentieel onderdeel van het ontwikkelingsproces en vereist een volledig begrip van de verschillende betrokken technologieën en protocollen.
In dit blog bespreken we hoe je eenvoudig een testsuite kunt maken om de inlogflow voor je applicaties te testen.
Ik zal het Yenlo Connext Platform gebruiken als de identiteitsprovider om ons de authenticatieservice te bieden. Yenlo Connext is een 24/7 gehoste en beheerde cloudoplossing op basis van WSO2-technologie.
Deze Selenium-test kan de authenticatie testen wanneer er geen meervoudige authenticatie zoals E-mail OTP/SMS OTP is ingeschakeld. De officiële pagina van Selenium raadt ook aan om basisauthenticatie te proberen zonder meervoudige authenticatie.
Hoe stel ik een testomgeving in voor het testen van authenticatie voor webapplicaties?
De eerste stap in het testen van het authenticatieproces van een webapplicatie is het instellen van de testomgeving. Dit omvat meestal het installeren van Selenium, evenals aanvullende software of bibliotheken die nodig zijn om met de webapplicatie te communiceren. Het is ook belangrijk om een goed begrip te hebben van het authenticatieproces en de verschillende stappen die daarbij komen kijken, en van de gegevens die nodig zijn om de tests uit te voeren.
Voor de reikwijdte van dit blog gebruiken we een applicatie die het OIDC-protocol gebruikt voor authenticatie met de identiteitsprovider in het Connext-platform. Je kunt de officiële documentatie van WSO2 raadplegen om erachter te komen hoe je eenvoudig op OIDC gebaseerde authenticatie kunt inschakelen voor je applicatie. Door het Yenlo Connext-platform te gebruiken, hoef je je geen zorgen te maken over de details van configuraties, want deze beheerde cloudservice neemt je dit werk uit handen. Ik zal de OIDC-flow hier niet uitleggen, aangezien onze focus in dit blog meer ligt op het testen van het authenticatieproces.
Ik maak een maven-project voor het schrijven van onze testsuite. Hieronder staat een voorbeeld van een maven pom-bestand met de nodige afhankelijkheden en plug-ins.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ConnextSeleniumTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ConnextSeleniumTest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.7.2</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
</plugins>
</build>
</project>
Vereiste afhankelijkheden en plug-ins voor geautomatiseerd testen met Selenium en TestNG
Belangrijkste afhankelijkheden:
- Selenium-java: dit is een java-taalbinding voor de Selenium WebDriver, een populaire open-sourcetool voor het automatiseren van webbrowsers. De selenium-java-component biedt een handige manier om vanuit een Java-programma met webbrowsers te communiceren, waardoor ontwikkelaars gemakkelijk geautomatiseerde tests kunnen schrijven, gegevens kunnen scrapen of andere geautomatiseerde taken op internet kunnen uitvoeren.
- TestNG: dit is een testframework dat wordt gebruikt bij de ontwikkeling van Java-applicaties. Het biedt een uitgebreid testframework dat het schrijven, beheren en uitvoeren van tests voor Java-applicaties eenvoudiger maakt, waardoor de kwaliteit van de software en de efficiëntie van het testproces worden verbeterd.
Plugins:
- Maven-surefire-plug-in: dit is een plug-in in Apache Maven die wordt gebruikt om tests uit te voeren en rapporten te genereren. Deze plug-in wordt meestal gebruikt om tests uit te voeren die in Java zijn geschreven met de testframeworks Junit of TestNG. De maven-surefire-plug-in biedt rapporten in verschillende indelingen, zoals platte tekst, XML en HTML. De rapporten geven informatie over de testresultaten, zoals het aantal uitgevoerde, geslaagde en mislukte tests, en de details van de testcases. De rapporten helpen ook om te identificeren welke tests zijn mislukt en bieden informatie die kan worden gebruikt om de tests te debuggen en te repareren.
Zodra de testomgeving is ingesteld, is de volgende stap het schrijven van testcases die verschillende scenario’s en gebruikersinteracties met de webapplicatie simuleren. Zo kun je bijvoorbeeld testcases maken die verifiëren dat gebruikers succesvol kunnen inloggen en toegang hebben tot hun account, evenals testcases die verifiëren dat gebruikers geen toegang kunnen krijgen tot hun account als ze onjuiste inloggegevens opgeven.
De volgende afbeelding toont de basispakketstructuur die ik heb gebruikt.
Testomgeving instellen in Selenium met @BeforeMethod en @AfterMethod-annotaties van TestNG
Voordat we testcases schrijven, moeten we een methode implementeren om de testomgeving in te stellen. TestNG biedt @BeforeMethod-annotatie om bepaalde methoden uit te voeren vóór elke testmethode in een testklasse. Zie het volgende voorbeeld:
@BeforeMethod
public void setUp() {
ChromeOptions chromeOptions = new ChromeOptions();
//To run in headless mode
chromeOptions.addArguments(Constants.HEADLESS_MODE);
driver = new ChromeDriver(chromeOptions);
js = (JavascriptExecutor) driver;
driver.get(Constants.URL);
System.out.println("Visiting the CCP portal and redirected to login page");
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Constants.IMPLICIT_WAIT, Constants.TIME_UNIT);
}
In deze methode kunnen we de Selenium-webdriver initialiseren. Aangezien ik mijn tests met de Chrome-browser ga uitvoeren, heb ik deze geïnitialiseerd met Chrome-driver. Je kunt elke driver gebruiken waarmee je wilt testen.
Ik heb de headless-modus gebruikt voor het uitvoeren van tests, omdat deze sneller en efficiënter is en er geen browser verschijnt telkens wanneer een test wordt uitgevoerd. Het is handig als je je tests uitvoert op een server die alleen de headless-modus ondersteunt. Om de headless-modus te bereiken, kun je ChromeOptions gebruiken en instellen.
De driver.get ()-methode in Selenium wordt gebruikt om een webpagina in de browser te openen. Er wordt een URL genomen als argument en de browser wordt naar die URL genavigeerd. De methode wacht tot de pagina volledig is geladen en geeft de controle terug aan het testscript. Zodra de pagina is geladen, kan het testscript verschillende acties op de pagina uitvoeren, zoals het klikken op knoppen, het invullen van formulieren, enz. De URL die ik hier heb gebruikt, is de URL van het authenticatie-eindpunt van het Yenlo Connext-platform .
Example: https://<hostname-connext-idp>/oauth2/authorize?response_type=code&client_id=<client-id-of-the-application>&redirect_uri=<URL-of-the-application>
Als het laden van elementen van je pagina varieert, is het verstandig om de methode driver.manage().time-outs().implicitlyWait() in te stellen. Je kunt instellen hoelang de driver wacht tot een element op de pagina aanwezig is voordat deze een fout genereert. Dit is van toepassing op alle elementen binnen het bereik van de WebDriver-instantie, dus als de driver het gewenste element niet onmiddellijk kan vinden, zal deze ernaar blijven zoeken gedurende het opgegeven tijdsinterval. Omdat sommige elementen in mijn applicatie tijd nodig hebben om te laden, heb ik de impliciete wachttijd ingesteld op 30 seconden.
Op een vergelijkbare manier hebben we ook een methode nodig die na elke testcase wordt uitgevoerd voor het opschonen van testgegevens, de testomgeving of andere veelvoorkomende taken voor het opschonen van tests.
Voorbeeld:
@AfterMethod
public void tearDown() {
driver.quit();
}
Voorbeeldtestcases om geslaagde en mislukte gebruikersaanmelding in het Yenlo Connext Platform te verifiëren
Nu kunnen we beginnen met het schrijven van onze testcases. Hieronder staan twee voorbeeldtestcases om te testen. De eerste simuleert een succesvolle gebruikersaanmelding en verifieert dat gebruikers succesvol kunnen inloggen. En de tweede testcase simuleert een mislukte aanmelding en verifieert dat gebruikers geen toegang hebben tot hun account als ze onjuiste inloggegevens opgeven.
@Test (priority = 1)
public void testCCPLogin() {
//Create login page factory object.
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.USER_NAME, Constants.PASSWORD);
landingToApplication();
}
@Test (priority = 2)
private void testIncorrectUsername(){
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.INCORRECT_USER_NAME,Constants.PASSWORD);
redirectingToErrorMessage();
}
Om een methode als een testcase te markeren, moet je @Test-annotatie gebruiken in TestNG. Vervolgens kun je de eigenschap ‘priority’ gebruiken om de volgorde van testuitvoering te specificeren. Het laagste nummer wordt als eerste uitgevoerd.
Voorbeeldtestgevallen voor het testen van beveiligingsfuncties en UI-functionaliteit van inlogpagina’s
Naast deze basistestcases is het ook belangrijk om de verschillende beveiligingsfuncties te testen die doorgaans worden gebruikt in webapplicaties om gebruikersgegevens te beschermen en ongeautoriseerde toegang te voorkomen. Zo kun je bijvoorbeeld testen dat gebruikers wordt gevraagd hun wachtwoord na een bepaalde periode te wijzigen of dat het wachtwoordveld wordt gemaskeerd. En je kunt testen op SSO-gedrag van je applicaties.
Naast de inloggerelateerde flows, kun je ook testen of de basisfuncties van de gebruikersinterface correct werken voor je inlogpagina’s, of dat de benodigde logo-afbeeldingen aanwezig zijn.
Hieronder volgt een voorbeeldtestcase die controleert op kapotte afbeeldingen op de inlogpagina.
@Test(priority = 3)
private void testBrokenImages(){
ccpLoginPage = new CCPLoginPage(driver);
int brokenImagesCount = 0;
List<WebElement> imageList = ccpLoginPage.images;
System.out.println("The page under test has " + imageList.size() + " images.");
for (WebElement image : imageList) {
if (image != null) {
String imagePath = image.getAttribute("src");
if (!isImageAvailable(imagePath)) {
brokenImagesCount++;
System.out.println(image.getAttribute("outerHTML") + " is broken.");
} else {
System.out.println("Image at " + image.getAttribute("outerHTML") + " is available");
}
}
}
System.out.println("The Connext Login page"+ " has " + brokenImagesCount + " broken images");
}
private boolean isImageAvailable(String imagePath) {
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(imagePath);
try {
HttpResponse response = client.execute(request);
int responseCode = response.getStatusLine().getStatusCode();
/* For valid images, the HttpStatus will be 200 */
if (responseCode != 200) {
return false;
} else {
return true;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
We moeten een HTTP GET-oproep doen naar het afbeeldingspad om te controleren of de afbeeldingen beschikbaar zijn. Daarom moeten we de ‘httpclient’-afhankelijkheid toevoegen aan ons maven-project.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
Voorbeeldtestcases voor versleuteling en SSO-functies
Hieronder volgt een voorbeeld van een testcase die controleert of het wachtwoordveld is gemaskeerd.
@Test(priority = 4)
public void testPasswordEncryption() {
ccpLoginPage = new CCPLoginPage(driver);
WebElement passwordElement = ccpLoginPage.getPasswordElement();
boolean isEncrypted = passwordElement.getAttribute("type").equals("password");
Assert.assertEquals(isEncrypted, true, "Password field is not masked.");
}
Hieronder volgt een voorbeeld van een testcase waarin de SSO-functie wordt getest.
@Test(priority = 5)
public void testSSO() throws AWTException {
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.USER_NAME, Constants.PASSWORD);
landingToApplication();
//Use a url of an application that is SSOed with the CCP portal
switchToNewTab("https://test-portal.com/ssoapplication/dashboard");
}
private void switchToNewTab(String url) {
String currentHandle = driver.getWindowHandle();
((JavascriptExecutor) driver).executeScript("window.open()");
Set<String> handles = driver.getWindowHandles();
for (String handle : handles) {
if (!handle.equalsIgnoreCase(currentHandle)) {
driver.switchTo().window(handle);
driver.get(url);
landingToSSOApplication();
}
}
}
private void landingToApplication(){
ccpHomePage = new CCPHomePage(driver);
ccpHomePage.waitUntilHomePageLoads();
String applicationTitle = driver.getTitle();
String expectedApplicationTitle = "Connext Customer Care Portal - Dashboard";
Assert.assertEquals(applicationTitle,expectedApplicationTitle,"Application page title doesn't match");
String expectedApplicationUrl="https://test-portal.com/application/dashboard";
String applicationUrl= driver.getCurrentUrl();
Assert.assertEquals(applicationUrl,expectedApplicationUrl,"Application url after login is not a match.");
}
private void landingToSSOApplication(){
ccpHomePage = new CCPHomePage(driver);
ccpHomePage.waitUntilHomePageLoads();
String applicationTitle = driver.getTitle();
String expectedApplicationTitle = "Connext Customer Care Portal - Dashboard";
Assert.assertEquals(applicationTitle,expectedApplicationTitle,"Application page title doesn't match");
String expectedApplicationUrl="https://test-portal.com/ssoapplication/dashboard";
String applicationUrl= driver.getCurrentUrl();
Assert.assertEquals(applicationUrl,expectedApplicationUrl,"Application url after login is not a match.");
}
De ‘switchToNewTab’-methode zorgt voor het openen van een ander browsertabblad binnen dezelfde browsersessie, en laadt een andere applicatie die is ingelogd bij de eerdere applicatie.
De ‘landingToApplication’-methode controleert of de gebruiker is ingelogd bij de applicatie.
De ‘landingToSSOApplication’-methode, controleert of de gebruiker is ingelogd bij de andere SSO-applicatie.
Stop hier niet! Bonus Selenium-testcaseklasse wacht op u aan het einde van deze blog.
POM en Page Factory-klasse gebruiken voor het testen van het authenticatieproces
Een van de uitdagingen bij het testen van het authenticatieproces is dat er vaak interactie nodig is met meerdere pagina’s, elementen en formulieren binnen de webapplicatie. Voor een eenvoudiger en efficiënter proces kiezen veel testers ervoor om het Page Object Model (POM)-ontwerppatroon te gebruiken, samen met de Page Factory-klasse, die is ingebouwd in het Selenium-framework. Met het POM-patroon kunnen testers de details van interactie met de verschillende elementen van een pagina inkapselen in herbruikbare objecten waarnaar gemakkelijk kan worden verwezen vanuit meerdere testcases.
De klasse Page Factory is een krachtige tool voor het implementeren van het POM-patroon, omdat testers hiermee de elementen van een pagina kunnen definiëren met behulp van annotaties en vervolgens eenvoudig naar die elementen kunnen verwijzen vanuit hun testcases. Een inlogpagina kan bijvoorbeeld een tekstvak voor de gebruikersnaam, een tekstvak voor het wachtwoord en een verzendknop hebben. Met de klasse Page Factory zijn deze elementen gemakkelijk in één klasse te definiëren en kan er vervolgens vanuit meerdere testgevallen naar worden verwezen.
Als u onze testcases volgt, ziet u verwijzingen naar klassen genaamd “CCPLoginPage” en “CCPHomePage”.
Bijvoorbeeld:
@Test(priority = 1)
public void testCCPLogin() {
//Create login page factory object.
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.USER_NAME, Constants.PASSWORD);
landingToApplication();
}
Die twee klassen zijn de “Page Factory-klassen” die respectievelijk de aanmeldingspagina en de startpagina van de applicatie vertegenwoordigen.
U kunt de @FindBy-annotatie gebruiken in een klasseattribuut en deze converteren naar een Page Factory-klasse in Selenium.
Raadpleeg het voorbeeld voor het markeren van velden voor gebruikersnaam, wachtwoord en aanmeldknop in de Page Factory-klasse CCPLoginPage.
public class CCPLoginPage {
private WebDriver driver;
@FindBy(id="username")
WebElement usernameElement;
@FindBy(id="password")
WebElement passwordElement;
@FindBy(css=".wr-btn")
WebElement loginButtonElement;
public CCPLoginPage (WebDriver webDriver){
this.driver = webDriver;
PageFactory.initElements(driver, this);
}
De bovenstaande klasse wordt toegewezen aan de volgende UI-elementen op de aanmeldpagina.
<inputid=”gebruikersnaam” name=”gebruikersnaam” type=”tekst” class=”besturingselement formulier” tabindex=”0″ placeholder=”gebruikersnaam” required autofocus>
<inputid=”wachtwoord” name=”wachtwoord” type=”wachtwoord” class=”besturingselement formulier” placeholder=”wachtwoord” autocomplete=”uit”>
<button class=”wr-btn grey-bg col-xs-12 col-md-12 col-lg-12 margin-bottom-double” type=”verzenden”> Login </button>
Om deze klasse als een Page Factory-object te laten werken, moet u PageFactory.initElements() binnen de constructor aanroepen.
Vervolgens kunt u methoden binnen die klasse schrijven, waarmee de waarden voor die elementen worden ingesteld om vervolgens de informatie in te dienen. De volledige broncode voor CCPLoginPage en CCPHomePage is te vinden in het bonusgedeelte aan het einde van deze blog.
Beweringen in Selenium-testcases
Beweringen worden gebruikt om het verwachte gedrag van een softwaretoepassing tijdens het testen te valideren. Ze zijn een belangrijk onderdeel van geautomatiseerd testen en helpen ervoor te zorgen dat de applicatie correct functioneert. Een bewering controleert een specifieke voorwaarde, en als niet aan de voorwaarde wordt voldaan, mislukt de bewering, wordt uit uitzondering gegenereerd en de testcase als mislukt gemarkeerd. Enkele veelgebruikte beweringen in Selenium en andere frameworks zijn:
- assertEquals: met deze bewering wordt gecontroleerd of twee waarden gelijk zijn en een uitzondering gegenereerd als dat niet het geval is.
- assertTrue: met deze bewering wordt gecontroleerd of een voorwaarde waar is, en een uitzondering gegenereerd als deze onwaar is.
- assertFalse: met deze bewering wordt gecontroleerd of een voorwaarde onwaar is, en een uitzondering gegenereerd als deze waar is.
- assertNull: met deze bewering wordt gecontroleerd of een waarde nul is, en een uitzondering gegenereerd als dit niet het geval is.
- assertNotNull: met deze bewering wordt gecontroleerd of een waarde niet nul is, en een uitzondering gegenereerd als dat zo is.
Deze beweringen worden gebruikt in combinatie met testframeworks zoals TestNG, JUnit, enz. om het gedrag van de softwareapplicatie bij het testen te valideren.
In onze testcases hebben we de volgende beweringen gedaan:
Aanmelden bij applicatie:
- de titel van de aanmeldpagina bevestigen
- de titel van de applicatiepagina bevestigen na aanmelden bij de applicatie
- applicatie-URL bevestigen na aanmelden bij de applicatie
Onjuiste gebruikersnaam:
- de titel van de aanmeldpagina bevestigen
- bericht ‘aanmelding mislukt’ bevestigen
Wachtwoord maskeren:
- bevestigen dat wachtwoordveld is gemaskeerd of niet
SSO:
- applicatie-URL en titel bevestigen vóór SSO
- pplicatie-URL en titel van de andere SSO-applicatie bevestigen, na SSO
TestNG-framework instellen voor uitvoeren test
Nu kunt u uw testklasse registreren in het TestNG-framework door de volgende invoer toe te voegen aan testng.xml.
<suite name="Suite1" verbose="1" >
<test name="CCPLogin" >
<classes>
<class name="org.yenlo.sample.CCPLoginSeleniumTest" />
</classes>
</test>
</suite>
Ga vervolgens naar uw projectroot en voer de opdracht ‘mvn test’ uit om de tests uit te voeren.
Zodra de testuitvoering is voltooid, gaat u naar de map ’target/surefire-reports’ en kunt u verschillende rapporten van de testuitvoering bekijken.
Conclusie: het authenticatieproces van een webapplicatie testen
Tot slot is het belangrijk om het authenticatieproces continu te testen als onderdeel van het ontwikkelproces. Dit kan door het regelmatig uitvoeren van geautomatiseerde tests, maar ook door middel van handmatige tests om te controleren of het authenticatieproces correct blijft functioneren als er wijzigingen in de webapplicatie worden aangebracht.
Concluderend, het testen van het authenticatieproces van een webapplicatie is een belangrijke en complexe taak die een diepgaand begrip vereist van de verschillende betrokken technologieën en protocollen, evenals een duidelijk begrip van het authenticatieproces zelf. Door Selenium te gebruiken in combinatie met het Page Object Model en Page Factory, kunnen testers eenvoudig testcases schrijven en uitvoeren die echte gebruikersinteracties met de webapplicatie simuleren en controleren of het authenticatieproces correct werkt. Door continu testen uit te voeren als onderdeel van het ontwikkelingsproces kunnen ontwikkelaars ervoor zorgen dat het authenticatieproces veilig en functioneel blijft terwijl de webapplicatie in de loop van de tijd evolueert.
BONUS: voorbeelden van Selenium-testcaseklasse voor webapplicaties
- Selenium-testcaseklasse.
package org.yenlo.sample;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.*;
import org.yenlo.sample.pagefactory.CCPHomePage;
import org.yenlo.sample.pagefactory.CCPLoginPage;
import org.yenlo.sample.pagefactory.util.Constants;
import java.awt.*;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Set;
public class CCPLoginSeleniumTest {
private WebDriver driver;
JavascriptExecutor js;
//Login page element repository
CCPLoginPage ccpLoginPage;
CCPHomePage ccpHomePage;
@BeforeMethod
public void setUp() {
ChromeOptions chromeOptions = new ChromeOptions();
//To run in headless mode
chromeOptions.addArguments(Constants.HEADLESS_MODE);
driver = new ChromeDriver(chromeOptions);
js = (JavascriptExecutor) driver;
driver.get(Constants.URL);
System.out.println("Visiting the CCP portal and redirected to login page");
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Constants.IMPLICIT_WAIT, Constants.TIME_UNIT);
}
@AfterMethod
public void tearDown() {
driver.quit();
}
@Test(priority = 1)
public void testCCPLogin() {
//Create login page factory object.
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.USER_NAME, Constants.PASSWORD);
landingToApplication();
}
@Test(priority = 2)
private void testIncorrectUsername(){
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.INCORRECT_USER_NAME, Constants.PASSWORD);
redirectingToErrorMessage();
}
@Test(priority = 3)
private void testBrokenImages(){
ccpLoginPage = new CCPLoginPage(driver);
int brokenImagesCount = 0;
List<WebElement> imageList = ccpLoginPage.images;
System.out.println("The page under test has " + imageList.size() + " images.");
for (WebElement image : imageList) {
if (image != null) {
String imagePath = image.getAttribute("src");
if (!isImageAvailable(imagePath)) {
brokenImagesCount++;
System.out.println(image.getAttribute("outerHTML") + " is broken.");
} else {
System.out.println("Image at " + image.getAttribute("outerHTML") + " is available");
}
}
}
System.out.println("The Connext Login page"+ " has " + brokenImagesCount + " broken images");
}
@Test(priority = 4)
public void testPasswordEncryption() {
ccpLoginPage = new CCPLoginPage(driver);
WebElement passwordElement = ccpLoginPage.getPasswordElement();
boolean isEncrypted = passwordElement.getAttribute("type").equals("password");
Assert.assertEquals(isEncrypted, true, "Password field is not masked.");
}
@Test(priority = 5)
public void testSSO() throws AWTException {
ccpLoginPage = new CCPLoginPage(driver);
assertPageTitle();
ccpLoginPage.loginToCCP(Constants.USER_NAME, Constants.PASSWORD);
landingToApplication();
//Use a url of an application that is SSOed with the CCP portal
switchToNewTab(" https://test-portal.com/ssoapplication/dashboard ");
}
private void switchToNewTab(String url) {
String currentHandle = driver.getWindowHandle();
((JavascriptExecutor) driver).executeScript("window.open()");
Set<String> handles = driver.getWindowHandles();
for (String handle : handles) {
if (!handle.equalsIgnoreCase(currentHandle)) {
driver.switchTo().window(handle);
driver.get(url);
landingToSSOApplication();
}
}
}
/**
* Verify login page title.
*/
private void assertPageTitle() {
String loginPageTitle = driver.getTitle();
String expectedLoginPageTitle = "Connext Identity Server";
Assert.assertEquals(loginPageTitle, expectedLoginPageTitle, "Connext login page title doesn't match");
}
private void landingToApplication(){
ccpHomePage = new CCPHomePage(driver);
ccpHomePage.waitUntilHomePageLoads();
String applicationTitle = driver.getTitle();
String expectedApplicationTitle = "Connext Customer Care Portal - Dashboard";
Assert.assertEquals(applicationTitle,expectedApplicationTitle,"Application page title doesn't match");
String expectedApplicationUrl="https://test-portal.com/application/dashboard";
String applicationUrl= driver.getCurrentUrl();
Assert.assertEquals(applicationUrl,expectedApplicationUrl,"Application url after login is not a match.");
}
/**
* Method to load the application that is in SSO with the CCP portal.
* Here I have used the same CCP portal. But you can update the method to use a different application URL.
*/
private void landingToSSOApplication(){
ccpHomePage = new CCPHomePage(driver);
ccpHomePage.waitUntilHomePageLoads();
String applicationTitle = driver.getTitle();
String expectedApplicationTitle = "Connext Customer Care Portal - Dashboard";
Assert.assertEquals(applicationTitle,expectedApplicationTitle,"Application page title doesn't match");
String expectedApplicationUrl=" https://test-portal.com/ssoapplication/dashboard ";
String applicationUrl= driver.getCurrentUrl();
Assert.assertEquals(applicationUrl,expectedApplicationUrl,"Application url after login is not a match.");
}
private void redirectingToErrorMessage(){
ccpLoginPage.waitUntilErrorMessageLoads();
assertFailedLoginMessage();
}
private void assertFailedLoginMessage(){
String errorNotification = ccpLoginPage.getFailedLoginMessage();
String expectedErrorNotification = "Login failed! Please recheck the username and password and try again.";
Assert.assertEquals(errorNotification,expectedErrorNotification,"Expected error message is not matching");
}
private boolean isImageAvailable(String imagePath) {
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(imagePath);
try {
HttpResponse response = client.execute(request);
int responseCode = response.getStatusLine().getStatusCode();
/* For valid images, the HttpStatus will be 200 */
if (responseCode != 200) {
return false;
} else {
return true;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- Page Factory-klassen CCPLoginPage en CCPHomePage.
package org.yenlo.sample.pagefactory;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.List;
public class CCPLoginPage {
private WebDriver driver;
@FindBy(id="username")
WebElement usernameElement;
@FindBy(id="password")
WebElement passwordElement;
@FindBy(css=".wr-btn")
WebElement loginButtonElement;
@FindBy(className = "alert-danger")
WebElement failedLoginNotificationElement;
@FindBy(tagName = "img")
public List<WebElement> images;
String failedLoginNotificationClassName = "alert-danger";
public CCPLoginPage(WebDriver webDriver){
this.driver = webDriver;
PageFactory.initElements(driver, this);
}
//Set username in username text box
public void setUsername(String username){
usernameElement.clear();
usernameElement.sendKeys(username);
System.out.println("Entering the username");
}
//Set password in password text box
public void setPassword(String password){
passwordElement.clear();
passwordElement.sendKeys(password);
System.out.println("entering the password");
}
//Submit username & password
public void clickLogin(){
loginButtonElement.click();
System.out.println("Clicking login button");
}
/**
* Method to call from the testcase, to login a user
*/
public void loginToCCP(String username, String password){
//fill username
this.setUsername(username);
//fill password
this.setPassword(password);
//click login button
this.clickLogin();
}
public void waitUntilErrorMessageLoads(){
WebDriverWait wdw = new WebDriverWait(driver, Duration.ofSeconds(30)); // wait up to 30 seconds
wdw.until(ExpectedConditions.visibilityOfElementLocated(By.className(failedLoginNotificationClassName)));
}
public String getFailedLoginMessage(){
return failedLoginNotificationElement.getText();
}
public WebElement getPasswordElement(){
return passwordElement;
}
}
package org.yenlo.sample.pagefactory;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
/**
* CCP home page web element repository
*/
public class CCPHomePage {
private WebDriver driver;
String ccpHomePageElementXpath = "//span[text()='Dashboard']";
public CCPHomePage(WebDriver webDriver){
this.driver = webDriver;
PageFactory.initElements(driver, this);
}
/**
* Method to wait until CCP home page loads after login.
*/
public void waitUntilHomePageLoads(){
WebDriverWait wdw = new WebDriverWait(driver, Duration.ofSeconds(30)); // wait up to 30 seconds
wdw.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(ccpHomePageElementXpath)));
}
}