Selenium WebDriver Recipes in C#: Practical Testing Solutions for Selenium WebDriver [3 ed.] 9798868800221


157 65 8MB

English Pages [331] Year 2024

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Table of Contents
About the Authors
Preface
Chapter 1: Introduction
Selenium WebDriver
Selenium Language Bindings
Set Up the Development Environment - Visual Studio Code
Prerequisites
Set Up Visual Studio Code
Cross-Browser Testing
Chrome
Firefox
Safari
Edge
Internet Explorer
Visual Studio Unit Testing Framework
Visual Studio Unit Testing Framework Fixtures
Alternative Framework: NUnit
Run Recipe Scripts
Run Test(s) in VS Code
Find the Test Case
Run an Individual Test Case
Run All Test Cases in a Test Script File
Run Tests from the Command Line
Chapter 2: Locating Web Elements
Start a Browser
Inspect a Web Element in a Browser
Find an Element by ID
Find an Element by Name
Find an Element by Link Text
Find an Element by Partial Link Text
Find an Element by XPath
Find an Element by a Tag Name
Find an Element by Class
Find an Element by CSS Selector
Find an Element by Relative Locators
Chain FindElement to Find Child Elements
Find Multiple Elements
Locate a Web Element That Disappears After Inspect
Chapter 3: Hyperlink
Click a Link by Text
Click a Link by ID
Click a Link by Partial Text
Click a Link by XPath
Click the Nth Link with Exact Same Label
Click the Nth Link by CSS Selector
Verify If a Link Is Present or Not
Get Link Data Attributes
Test If a Link Opens a New Browser Window
Chapter 4: Button
Click a Button by Text
Click a Form Button by Text
Submit a Form
Click a Button by ID
Click a Button by Name
Click an Image Button
Click a Button via JavaScript
Assert If a Button Is Present
Assert If a Button Is Enabled or Disabled
Chapter 5: TextField and TextArea
Enter Text into a Text Field by Name
Enter Text into a Text Field by ID
Enter Text into a Password Field
Clear a Text Field
Enter Text into a Multiline Text Area
Assert a Value
Focus on a Control
Set a Value to a Read-Only or Disabled Text Field
Set and Assert the Value of a Hidden Field
Chapter 6: Radio Button
Select a Radio Button
Clear the Radio Option Selection
Assert a Radio Option Is Selected
Iterate Radio Buttons in a Radio Group
Click the Nth Radio Button in a Group
Click a Radio Button by the Following Label
Customized Radio Buttons with iCheck
Chapter 7: CheckBox
Check by Name
Check by Id
Uncheck a Checkbox
Assert a Checkbox Is Checked (or Not)
Chain FindElement to Find Child Elements
Customized Checkboxes using iCheck
Chapter 8: Select List
Import Selenium.Support
Select an Option by Text
Select an Option by a Value
Select an Option by an Index
Send Keys to the Select Element
Select an Option by Iterating All Options
Select Multiple Options
Clear One Selection
Clear All Selections
Assert a Selected Option
Assert a Label or Value of a Select List
Assert Multiple Selections
Chapter 9: Navigation and Browser
Go to a URL
Visit Pages Within a Site
Open Browser in a Certain Size
Maximize the Browser Window
Move the Browser Window
Minimize a Browser Window
Scroll Focus to Control via JavaScript
Scroll with Scroll Wheel Actions
Switch Between Browser Windows or Tabs
New Browser Window or Tab
Open and Close Browser Tabs Using JavaScript
Remember Current Web Page URL and Come Back to It Later
Chapter 10: Assertion
Assert a Page Title
Assert Page Text
Assert the Page Source
Assert Label Text
Assert Span Text
Assert Div Text or HTML
Assert Table Text
Assert Text in a Table Cell
Assert Text in a Table Row
Assert If an Image Is Present
Assert Element Location and Width
Assert Element CSS Style
Assert JS Errors on a Web Page
Chapter 11: Frames
Testing Frames
Find a Frame with FindElement
Test an IFrame
Test Multiple IFrames
Chapter 12: Testing AJAX
Pause for a Specific Duration of Time
Explicit Waits Until Time Out
Implicit Waits Until Time Out
Fluent Waits Until Time Out
Create Your Own Polling Check Function
Refactor with a Reusable Function
Wait for the AJAX Call to Complete Using JQuery
Chapter 13: File Upload and Popup Dialogs
File Upload
File Upload with a Relative Path
JavaScript Popups
Handle JavaScript Popups Using Alert API
Handle JavaScript Popups with JavaScript
Modal Style Dialogs
Timeout on an Operation
Popup Handler Approach
Basic or Proxy Authentication Dialog
Bypass Basic Authentication by Embedding the Username and Password in the URL
Internet Explorer Modal Dialog
Chapter 14: Debugging Test Scripts
Print Text for Debugging
Write the Page Source or Element HTML into a File
Take a Screenshot
Leave the Browser Open After a Test Finishes
Debug Test Execution using Debuggers
Enable Breakpoints
Execute One Test Case in Debugging Mode
Step Over Test Execution
Chapter 15: Test Data
Get Data Dynamically
Get a Random Boolean Value
Get a Random Boolean Value with Timestamps
Generate a Number Within a Range
Get a Random Character
Get a Random String at a Fixed Length
Get a Random String in a Collection
Generate a Test File at a Fixed Size
Retrieve Data from a Database
Chapter 16: Browser Profile and Capabilities
Get Browser Type and Version
Set the Page Load Strategy
Set an HTTP Proxy for a Browser
Verify File Download in Chrome
Verify File Download in Firefox
Bypass Basic Authentication with the Firefox AutoAuth Plugin
Manage Cookies
Headless Browser Testing with Chrome
Test Responsive Websites
Chapter 17: Advanced User Interactions
Double-Click a Control
Move the Mouse to a Control (Mouse Over)
Click and Hold (Select Multiple Items)
Context Click (Right-Click a Control)
Drag-and-Drop
Drag a Slider
Send Key Sequences (Select All and Delete)
Chapter 18: HTML5 and JavaScript
HTML5 Email Type Field
HTML5 Time Field
Invoke the onclick JavaScript Event
Invoke JavaScript Events Such As OnChange
Scroll to the Bottom of a Page
Chosen - Standard Select
Chosen - Multiple Select
AngularJS Web Pages
Ember JS Web Pages
Faking Geolocation with JavaScript
Save an SVG Chart to a PNG Image
Save a Canvas to a PNG Image
Chapter 19: WYSIWYG HTML Editors
TinyMCE
CKEditor
SummerNote
CodeMirror
Chapter 20: Leveraging Programming
Throw Exceptions to Fail Tests
Ignorable Test Statement Error
Read an External File
Data-Driven Tests with Excel
Data-Driven Tests with CSV
Identify Element IDs with Dynamically Generated Long Prefixes
Sending Special Keys Such as Enter to an Element or Browser
Use of Unicode in Test Scripts
Extract a Group of Dynamic Data: Verify Search Results in Order
Verify Uniqueness of a Set of Data
Extract Dynamic Visible Data Rows from a Results Table
Extract Dynamic Text Following a Pattern Using Regex
Chapter 21: Optimization
Assert Text in a Page Source Is Faster Than the Text
Getting Text From a More Specific Element Is Faster
Avoid Programming If-Else Blocks If Possible
Use a Variable to Cache Unchanged Data
Enter Large Text into a Text Box
Use Environment Variables to Change Test Behaviors Dynamically
Testing a Web Site in Multiple Languages
Multi-Language Testing with Lookups
Chapter 22: Gotchas
Test Starts Browser But No Execution but a Blank Screen
Failed to Assert Copied Text in Browser
The Same Test Works for Chrome, But Not for IE
“unexpected tag name ‘input’”
Element Is Not Clickable or Visible
Chapter 23: Selenium 4
Chrome For Testing
New Browser Window or Tab
Basic Authentication via Register
Print to PDF
Save Element Screenshot
Drive Elements Inside a Shadow DOM
Relative Locators
Get the Element on the Right of a Checkbox
Get the Element(s) on the Left of a Checkbox
Above in a Table
Below in a Table
Near
Summary
Chapter 24: Selenium DevTools
Emulate Browser Crash
CDP Send Command
Emulate GEO Location
Emulate Locale
Emulate Timezone
Network Interception
Network Latency
Security: Ignore CertificateErrors
Chapter 25: Selenium Grid
Selenium Server Standalone
Execute Tests on a Remote Machine
Set Up Selenium Grid
Using Selenium Grid to Run Tests
Issues with Selenium Grid
Chapter 26: Continuous Testing
CI vs. CT
Preparation
Source Control Test Scripts in Git
Compile Test Scripts
Execute a Test Script from the Command Line
Execute a Test Script with JUnit XML
Set Up BuildWise Server
Set Up a Build Project
Run a Suite of C# tests in BuildWise
What’s the Magic?
Chapter 27: Case Studies
Verify Chart Generation
Test Design
Test Steps
Test Script
Drawing on a Canvas
Test Steps
Log In and Get to the Canvas Page
Set a Fixed Canvas and Choose an Anchor Point
Draw a Circle and Move It
Draw a Cross (Hand-Drawing Mode)
Add Text
Test Script
Automated Testing Elements on a Lazy Load Page
Test Design
Test Steps
Generate a User Guide with an Automation Script
Test Design
Test Steps
Generated Files
What Does It Look Like?
Summary
Broken Link Checker
Test Design
Test Steps
Afterword
Resources
Books
Web Sites
Tools
Practice Makes Perfect
Successful Test Automation
Appendix 1: Visual Studio IDE Setup
Setup
Prerequisite
Set Up Visual Studio Solution
Create a Test and Run It
Run Test(s) in Visual Studio
Find the Test Case
Run an Individual Test Case
Run All Test Cases in a Test Script File
Run All Test Scripts
Visual Studio IDE or VS Code?
Appendix 2: Visual Studio IDE Debugging Test Execution
Enable Breakpoints
Execute One Test Case in Debugging Mode
Step Over Test Execution
Index
Recommend Papers

Selenium WebDriver Recipes in C#: Practical Testing Solutions for Selenium WebDriver [3 ed.]
 9798868800221

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Selenium WebDriver Recipes in C# Practical Testing Solutions for Selenium WebDriver — Third Edition — Courtney Zhan, Edited by Zhimin Zhan

Selenium WebDriver Recipes in C# Practical Testing Solutions for Selenium WebDriver Third Edition

Courtney Zhan Zhimin Zhan

Selenium WebDriver Recipes in C#: Practical Testing Solutions for Selenium WebDriver, Third Edition Courtney Zhan Fitzgibbon, QLD, Australia

Zhimin Zhan Brisbane, QLD, Australia

ISBN-13 (pbk): 979-8-8688-0022-1 https://doi.org/10.1007/979-8-8688-0023-8

ISBN-13 (electronic): 979-8-8688-0023-8

Copyright © 2024 by Courtney Zhan, Edited by Zhimin Zhan This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar methodology now known or hereafter developed. Trademarked names, logos, and images may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, logo, or image we use the names, logos, and images only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. The use in this publication of trade names, trademarks, service marks, and similar terms, even if they are not identified as such, is not to be taken as an expression of opinion as to whether or not they are subject to proprietary rights. While the advice and information in this book are believed to be true and accurate at the date of publication, neither the authors nor the editors nor the publisher can accept any legal responsibility for any errors or omissions that may be made. The publisher makes no warranty, express or implied, with respect to the material contained herein. Managing Director, Apress Media LLC: Welmoed Spahr Acquisitions Editor: Melissa Duffy Development Editor: James Markham Coordinating Editor: Gryffin Winkler Copy Editor: Mary Behr Cover designed by eStudioCalamar Cover image by robert1029 from Pixabay (https://pixabay.com) Distributed to the book trade worldwide by Apress Media, LLC, 1 New York Plaza, New York, NY 10004, U.S.A. Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail [email protected], or visit www.springeronline.com. Apress Media, LLC is a California LLC and the sole member (owner) is Springer Science + Business Media Finance Inc (SSBM Finance Inc). SSBM Finance Inc is a Delaware corporation. For information on translations, please e-mail [email protected]; for reprint, paperback, or audio rights, please e-mail [email protected]. Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also available for most titles. For more information, reference our Print and eBook Bulk Sales web page at www.apress.com/bulk-sales. Any source code or other supplementary material referenced by the author in this book is available to readers on GitHub (https://github.com/Apress). For more detailed information, please visit https://www.apress.com/gp/services/source-code. Paper in this product is recyclable

This book is dedicated to my mum, Xindi.

Table of Contents About the Authors�����������������������������������������������������������������������������xvii Preface����������������������������������������������������������������������������������������������xix Chapter 1: Introduction������������������������������������������������������������������������1 Selenium WebDriver����������������������������������������������������������������������������������������������1 Selenium Language Bindings�������������������������������������������������������������������������������1 Set Up the Development Environment - Visual Studio Code���������������������������������4 Prerequisites���������������������������������������������������������������������������������������������������4 Set Up Visual Studio Code�������������������������������������������������������������������������������4 Cross-Browser Testing����������������������������������������������������������������������������������������11 Chrome����������������������������������������������������������������������������������������������������������11 Firefox�����������������������������������������������������������������������������������������������������������13 Safari�������������������������������������������������������������������������������������������������������������13 Edge��������������������������������������������������������������������������������������������������������������15 Internet Explorer��������������������������������������������������������������������������������������������15 Visual Studio Unit Testing Framework����������������������������������������������������������������15 Visual Studio Unit Testing Framework Fixtures���������������������������������������������18 Alternative Framework: NUnit�����������������������������������������������������������������������������19 Run Recipe Scripts����������������������������������������������������������������������������������������������20

v

Table of Contents

Run Test(s) in VS Code����������������������������������������������������������������������������������������20 Find the Test Case�����������������������������������������������������������������������������������������20 Run an Individual Test Case���������������������������������������������������������������������������21 Run All Test Cases in a Test Script File����������������������������������������������������������22 Run Tests from the Command Line����������������������������������������������������������������23

Chapter 2: Locating Web Elements�����������������������������������������������������25 Start a Browser���������������������������������������������������������������������������������������������������26 Inspect a Web Element in a Browser�������������������������������������������������������������27 Find an Element by ID������������������������������������������������������������������������������������28 Find an Element by Name������������������������������������������������������������������������������28 Find an Element by Link Text�������������������������������������������������������������������������29 Find an Element by Partial Link Text��������������������������������������������������������������29 Find an Element by XPath������������������������������������������������������������������������������29 Find an Element by a Tag Name��������������������������������������������������������������������31 Find an Element by Class������������������������������������������������������������������������������31 Find an Element by CSS Selector������������������������������������������������������������������32 Find an Element by Relative Locators�����������������������������������������������������������32 Chain FindElement to Find Child Elements����������������������������������������������������33 Find Multiple Elements����������������������������������������������������������������������������������34 Locate a Web Element That Disappears After Inspect�����������������������������������34

Chapter 3: Hyperlink���������������������������������������������������������������������������37 Click a Link by Text���������������������������������������������������������������������������������������������37 Click a Link by ID�������������������������������������������������������������������������������������������������38 Click a Link by Partial Text����������������������������������������������������������������������������������38 Click a Link by XPath�������������������������������������������������������������������������������������������38 Click the Nth Link with Exact Same Label����������������������������������������������������������40

vi

Table of Contents

Click the Nth Link by CSS Selector���������������������������������������������������������������������40 Verify If a Link Is Present or Not��������������������������������������������������������������������������41 Get Link Data Attributes��������������������������������������������������������������������������������������41 Test If a Link Opens a New Browser Window������������������������������������������������������42

Chapter 4: Button�������������������������������������������������������������������������������43 Click a Button by Text������������������������������������������������������������������������������������������44 Click a Form Button by Text��������������������������������������������������������������������������������44 Submit a Form����������������������������������������������������������������������������������������������������44 Click a Button by ID���������������������������������������������������������������������������������������������45 Click a Button by Name���������������������������������������������������������������������������������������46 Click an Image Button�����������������������������������������������������������������������������������������46 Click a Button via JavaScript������������������������������������������������������������������������������47 Assert If a Button Is Present�������������������������������������������������������������������������������47 Assert If a Button Is Enabled or Disabled������������������������������������������������������������47

Chapter 5: TextField and TextArea������������������������������������������������������49 Enter Text into a Text Field by Name�������������������������������������������������������������������50 Enter Text into a Text Field by ID�������������������������������������������������������������������������50 Enter Text into a Password Field�������������������������������������������������������������������������50 Clear a Text Field�������������������������������������������������������������������������������������������������50 Enter Text into a Multiline Text Area��������������������������������������������������������������������51 Assert a Value�����������������������������������������������������������������������������������������������������51 Focus on a Control����������������������������������������������������������������������������������������������51 Set a Value to a Read-Only or Disabled Text Field����������������������������������������������52 Set and Assert the Value of a Hidden Field���������������������������������������������������������53

vii

Table of Contents

Chapter 6: Radio Button���������������������������������������������������������������������55 Select a Radio Button������������������������������������������������������������������������������������������55 Clear the Radio Option Selection������������������������������������������������������������������������56 Assert a Radio Option Is Selected�����������������������������������������������������������������������57 Iterate Radio Buttons in a Radio Group���������������������������������������������������������������57 Click the Nth Radio Button in a Group�����������������������������������������������������������������58 Click a Radio Button by the Following Label�������������������������������������������������������58 Customized Radio Buttons with iCheck��������������������������������������������������������������59

Chapter 7: CheckBox��������������������������������������������������������������������������61 Check by Name���������������������������������������������������������������������������������������������������61 Check by Id���������������������������������������������������������������������������������������������������������61 Uncheck a Checkbox�������������������������������������������������������������������������������������������62 Assert a Checkbox Is Checked (or Not)���������������������������������������������������������������62 Chain FindElement to Find Child Elements���������������������������������������������������������62 Customized Checkboxes using iCheck���������������������������������������������������������������63

Chapter 8: Select List�������������������������������������������������������������������������65 Import Selenium.Support������������������������������������������������������������������������������������65 Select an Option by Text��������������������������������������������������������������������������������������66 Select an Option by a Value��������������������������������������������������������������������������������67 Select an Option by an Index������������������������������������������������������������������������������67 Send Keys to the Select Element������������������������������������������������������������������������67 Select an Option by Iterating All Options�������������������������������������������������������������68 Select Multiple Options���������������������������������������������������������������������������������������68 Clear One Selection���������������������������������������������������������������������������������������������69 Clear All Selections���������������������������������������������������������������������������������������������70

viii

Table of Contents

Assert a Selected Option�������������������������������������������������������������������������������������70 Assert a Label or Value of a Select List���������������������������������������������������������������70 Assert Multiple Selections����������������������������������������������������������������������������������71

Chapter 9: Navigation and Browser����������������������������������������������������73 Go to a URL���������������������������������������������������������������������������������������������������������73 Visit Pages Within a Site�������������������������������������������������������������������������������������73 Open Browser in a Certain Size��������������������������������������������������������������������������75 Maximize the Browser Window���������������������������������������������������������������������������75 Move the Browser Window���������������������������������������������������������������������������������75 Minimize a Browser Window�������������������������������������������������������������������������������76 Scroll Focus to Control via JavaScript����������������������������������������������������������������76 Scroll with Scroll Wheel Actions�������������������������������������������������������������������������77 Switch Between Browser Windows or Tabs��������������������������������������������������������78 New Browser Window or Tab������������������������������������������������������������������������������79 Open and Close Browser Tabs Using JavaScript�������������������������������������������������79 Remember Current Web Page URL and Come Back to It Later���������������������������80

Chapter 10: Assertion�������������������������������������������������������������������������83 Assert a Page Title����������������������������������������������������������������������������������������������83 Assert Page Text��������������������������������������������������������������������������������������������������83 Assert the Page Source���������������������������������������������������������������������������������������84 Assert Label Text�������������������������������������������������������������������������������������������������84 Assert Span Text�������������������������������������������������������������������������������������������������85 Assert Div Text or HTML��������������������������������������������������������������������������������������85 Assert Table Text�������������������������������������������������������������������������������������������������86 Assert Text in a Table Cell�����������������������������������������������������������������������������������88

ix

Table of Contents

Assert Text in a Table Row����������������������������������������������������������������������������������88 Assert If an Image Is Present������������������������������������������������������������������������������88 Assert Element Location and Width��������������������������������������������������������������������88 Assert Element CSS Style�����������������������������������������������������������������������������������89 Assert JS Errors on a Web Page��������������������������������������������������������������������������89

Chapter 11: Frames����������������������������������������������������������������������������91 Testing Frames����������������������������������������������������������������������������������������������������91 Find a Frame with FindElement��������������������������������������������������������������������������93 Test an IFrame����������������������������������������������������������������������������������������������������93 Test Multiple IFrames������������������������������������������������������������������������������������������95

Chapter 12: Testing AJAX�������������������������������������������������������������������97 Pause for a Specific Duration of Time�����������������������������������������������������������������98 Explicit Waits Until Time Out�������������������������������������������������������������������������������99 Implicit Waits Until Time Out�����������������������������������������������������������������������������100 Fluent Waits Until Time Out�������������������������������������������������������������������������������100 Create Your Own Polling Check Function����������������������������������������������������������101 Refactor with a Reusable Function�������������������������������������������������������������������102 Wait for the AJAX Call to Complete Using JQuery���������������������������������������������104

Chapter 13: File Upload and Popup Dialogs��������������������������������������107 File Upload��������������������������������������������������������������������������������������������������������107 File Upload with a Relative Path�����������������������������������������������������������������������109 JavaScript Popups��������������������������������������������������������������������������������������������109 Handle JavaScript Popups Using Alert API��������������������������������������������������110 Handle JavaScript Popups with JavaScript�������������������������������������������������111 Modal Style Dialogs������������������������������������������������������������������������������������������111

x

Table of Contents

Timeout on an Operation�����������������������������������������������������������������������������������112 Popup Handler Approach����������������������������������������������������������������������������������112 Basic or Proxy Authentication Dialog����������������������������������������������������������������113 Bypass Basic Authentication by Embedding the Username and Password in the URL�����������������������������������������������������������������������������������������115 Internet Explorer Modal Dialog�������������������������������������������������������������������������116

Chapter 14: Debugging Test Scripts�������������������������������������������������117 Print Text for Debugging������������������������������������������������������������������������������������117 Write the Page Source or Element HTML into a File�����������������������������������������118 Take a Screenshot���������������������������������������������������������������������������������������������118 Leave the Browser Open After a Test Finishes��������������������������������������������������119 Debug Test Execution using Debuggers������������������������������������������������������������120 Enable Breakpoints��������������������������������������������������������������������������������������120 Execute One Test Case in Debugging Mode������������������������������������������������121 Step Over Test Execution�����������������������������������������������������������������������������122

Chapter 15: Test Data�����������������������������������������������������������������������123 Get Data Dynamically����������������������������������������������������������������������������������������123 Get a Random Boolean Value����������������������������������������������������������������������������125 Get a Random Boolean Value with Timestamps������������������������������������������������126 Generate a Number Within a Range������������������������������������������������������������������127 Get a Random Character�����������������������������������������������������������������������������������127 Get a Random String at a Fixed Length������������������������������������������������������������127 Get a Random String in a Collection�����������������������������������������������������������������128 Generate a Test File at a Fixed Size������������������������������������������������������������������129 Retrieve Data from a Database�������������������������������������������������������������������������129

xi

Table of Contents

Chapter 16: Browser Profile and Capabilities�����������������������������������133 Get Browser Type and Version��������������������������������������������������������������������������133 Set the Page Load Strategy������������������������������������������������������������������������������134 Set an HTTP Proxy for a Browser����������������������������������������������������������������������135 Verify File Download in Chrome������������������������������������������������������������������������136 Verify File Download in Firefox�������������������������������������������������������������������������136 Bypass Basic Authentication with the Firefox AutoAuth Plugin������������������������137 Manage Cookies������������������������������������������������������������������������������������������������140 Headless Browser Testing with Chrome�����������������������������������������������������������141 Test Responsive Websites���������������������������������������������������������������������������������142

Chapter 17: Advanced User Interactions������������������������������������������145 Double-Click a Control��������������������������������������������������������������������������������������146 Move the Mouse to a Control (Mouse Over)������������������������������������������������������146 Click and Hold (Select Multiple Items)��������������������������������������������������������������147 Context Click (Right-Click a Control)�����������������������������������������������������������������148 Drag-and-Drop��������������������������������������������������������������������������������������������������148 Drag a Slider�����������������������������������������������������������������������������������������������������150 Send Key Sequences (Select All and Delete)����������������������������������������������������151

Chapter 18: HTML5 and JavaScript��������������������������������������������������153 HTML5 Email Type Field������������������������������������������������������������������������������������153 HTML5 Time Field���������������������������������������������������������������������������������������������154 Invoke the onclick JavaScript Event�����������������������������������������������������������������155 Invoke JavaScript Events Such As OnChange���������������������������������������������������156 Scroll to the Bottom of a Page��������������������������������������������������������������������������157 Chosen - Standard Select���������������������������������������������������������������������������������157 Chosen - Multiple Select�����������������������������������������������������������������������������������161

xii

Table of Contents

AngularJS Web Pages���������������������������������������������������������������������������������������166 Ember JS Web Pages����������������������������������������������������������������������������������������169 Faking Geolocation with JavaScript������������������������������������������������������������������171 Save an SVG Chart to a PNG Image�������������������������������������������������������������������172 Save a Canvas to a PNG Image�������������������������������������������������������������������������172

Chapter 19: WYSIWYG HTML Editors�������������������������������������������������175 TinyMCE������������������������������������������������������������������������������������������������������������175 CKEditor������������������������������������������������������������������������������������������������������������177 SummerNote�����������������������������������������������������������������������������������������������������178 CodeMirror��������������������������������������������������������������������������������������������������������179

Chapter 20: Leveraging Programming����������������������������������������������181 Throw Exceptions to Fail Tests��������������������������������������������������������������������������181 Ignorable Test Statement Error�������������������������������������������������������������������������183 Read an External File����������������������������������������������������������������������������������������184 Data-Driven Tests with Excel����������������������������������������������������������������������������185 Data-Driven Tests with CSV������������������������������������������������������������������������������189 Identify Element IDs with Dynamically Generated Long Prefixes����������������������190 Sending Special Keys Such as Enter to an Element or Browser�����������������������191 Use of Unicode in Test Scripts���������������������������������������������������������������������������192 Extract a Group of Dynamic Data: Verify Search Results in Order���������������������193 Verify Uniqueness of a Set of Data��������������������������������������������������������������������195 Extract Dynamic Visible Data Rows from a Results Table���������������������������������195 Extract Dynamic Text Following a Pattern Using Regex������������������������������������198

Chapter 21: Optimization������������������������������������������������������������������201 Assert Text in a Page Source Is Faster Than the Text����������������������������������������201 Getting Text From a More Specific Element Is Faster���������������������������������������202 xiii

Table of Contents

Avoid Programming If-Else Blocks If Possible��������������������������������������������������203 Use a Variable to Cache Unchanged Data���������������������������������������������������������204 Enter Large Text into a Text Box������������������������������������������������������������������������205 Use Environment Variables to Change Test Behaviors Dynamically������������������205 Testing a Web Site in Multiple Languages��������������������������������������������������������207 Multi-Language Testing with Lookups��������������������������������������������������������������209

Chapter 22: Gotchas�������������������������������������������������������������������������213 Test Starts Browser But No Execution but a Blank Screen�������������������������������213 Failed to Assert Copied Text in Browser������������������������������������������������������������215 The Same Test Works for Chrome, But Not for IE����������������������������������������������217 “unexpected tag name ‘input’”�������������������������������������������������������������������������218 Element Is Not Clickable or Visible��������������������������������������������������������������������219

Chapter 23: Selenium 4��������������������������������������������������������������������221 Chrome For Testing�������������������������������������������������������������������������������������������221 New Browser Window or Tab����������������������������������������������������������������������������224 Basic Authentication via Register���������������������������������������������������������������������224 Print to PDF�������������������������������������������������������������������������������������������������������225 Save Element Screenshot���������������������������������������������������������������������������������226 Drive Elements Inside a Shadow DOM��������������������������������������������������������������226 Relative Locators����������������������������������������������������������������������������������������������229 Get the Element on the Right of a Checkbox�����������������������������������������������229 Get the Element(s) on the Left of a Checkbox���������������������������������������������230 Above in a Table�������������������������������������������������������������������������������������������231 Below in a Table������������������������������������������������������������������������������������������233 Near�������������������������������������������������������������������������������������������������������������235 Summary�����������������������������������������������������������������������������������������������������237

xiv

Table of Contents

Chapter 24: Selenium DevTools��������������������������������������������������������239 Emulate Browser Crash������������������������������������������������������������������������������������240 CDP Send Command�����������������������������������������������������������������������������������������241 Emulate GEO Location���������������������������������������������������������������������������������242 Emulate Locale�������������������������������������������������������������������������������������������������243 Emulate Timezone���������������������������������������������������������������������������������������������244 Network Interception����������������������������������������������������������������������������������������244 Network Latency�����������������������������������������������������������������������������������������������245 Security: Ignore CertificateErrors����������������������������������������������������������������������247

Chapter 25: Selenium Grid����������������������������������������������������������������249 Selenium Server Standalone����������������������������������������������������������������������������250 Execute Tests on a Remote Machine����������������������������������������������������������������250 Set Up Selenium Grid����������������������������������������������������������������������������������������251 Using Selenium Grid to Run Tests���������������������������������������������������������������������254 Issues with Selenium Grid��������������������������������������������������������������������������������256

Chapter 26: Continuous Testing��������������������������������������������������������261 CI vs. CT������������������������������������������������������������������������������������������������������������263 Preparation�������������������������������������������������������������������������������������������������������264 Source Control Test Scripts in Git����������������������������������������������������������������������264 Compile Test Scripts������������������������������������������������������������������������������������265 Execute a Test Script from the Command Line��������������������������������������������266 Execute a Test Script with JUnit XML����������������������������������������������������������267 Set Up BuildWise Server�����������������������������������������������������������������������������������268 Set Up a Build Project���������������������������������������������������������������������������������������270 Run a Suite of C# tests in BuildWise�����������������������������������������������������������������272 What’s the Magic?��������������������������������������������������������������������������������������������273

xv

Table of Contents

Chapter 27: Case Studies������������������������������������������������������������������275 Verify Chart Generation�������������������������������������������������������������������������������������275 Test Design��������������������������������������������������������������������������������������������������275 Test Steps����������������������������������������������������������������������������������������������������276 Test Script����������������������������������������������������������������������������������������������������280 Drawing on a Canvas����������������������������������������������������������������������������������������281 Test Steps����������������������������������������������������������������������������������������������������282 Test Script����������������������������������������������������������������������������������������������������285 Automated Testing Elements on a Lazy Load Page�������������������������������������������287 Test Design��������������������������������������������������������������������������������������������������288 Test Steps����������������������������������������������������������������������������������������������������288 Generate a User Guide with an Automation Script��������������������������������������������290 Test Design��������������������������������������������������������������������������������������������������290 Test Steps����������������������������������������������������������������������������������������������������291 Generated Files��������������������������������������������������������������������������������������������293 What Does It Look Like?������������������������������������������������������������������������������294 Summary�����������������������������������������������������������������������������������������������������294 Broken Link Checker�����������������������������������������������������������������������������������������295 Test Design��������������������������������������������������������������������������������������������������295 Test Steps����������������������������������������������������������������������������������������������������295

Afterword������������������������������������������������������������������������������������������299 Resources�����������������������������������������������������������������������������������������303 Appendix 1: Visual Studio IDE Setup������������������������������������������������307 Appendix 2: Visual Studio IDE Debugging Test Execution����������������319 Index�������������������������������������������������������������������������������������������������323

xvi

About the Authors Courtney Zhan is a software development engineer at Amazon Australia. She is passionate about programming, end-to-end test automation, continuous testing, and graphic design. She shares tips and guides on test automation and continuous testing weekly on Medium at courtneyzhan.medium.com.

Zhimin Zhan is the founder and CTO of AgileWay Pty Ltd, Australia. As an advisor and coach, he helps organizations implement continuous testing with open technologies such as Selenium WebDriver and Appium. Zhimin is the creator of the TestWise Testing IDE and international award-winning BuildWise CT Server and an author.

xvii

Preface It has been nearly eight years since Selenium WebDriver Recipes for C# Third Edition was published. Since then, •

Selenium WebDriver v2 → v4



Visual Studio 2013 → 2022



The uprising of VS Code



Browser market changes mean IE and Edge (classic) are gone.

It is about time to update this book. There is one observation we want to point out. Before starting this edition, we opened the recipes and ran all tests in Visual Studio 2022, and 138 out of 171 tests passed. Please note this was Selenium WebDriver v2 (from 8 years ago) running against the latest Chrome browser (v114). The majority of the test failures were related to the changes in external websites and Firefox tests (which were not configured). This shows the high stability of WebDriver.

What’s New Selenium’s new features and improvements include the following: •

DOM shadow roots



Relative locators



Chrome DevTools

xix

Preface



New APIs



…and more!

What’s new in this book: •

New (about 15% more) and updated recipes



Using Visual Studio Code as the tool for developing/ executing test scripts



Executing Selenium C# tests in a BuildWise continuous testing server

Who Should Read This Book This book is for software testers or programmers who are writing (or want to learn how to write) automated tests with Selenium WebDriver. To get the most out of this book, basic C# coding skill is required.

How to Read This Book Typically, a recipe book serves as a reference, allowing readers to navigate directly to the specific topic of interest. For instance, if you encounter a challenge while testing a multiple select list and require guidance, you can refer to the Table of Contents and locate the relevant chapter. This book supports this style of reading. Since the recipes are arranged according to their levels of complexity, you will also be able to work through the book from the front to back if you are looking to learn test automation with Selenium WebDriver.

xx

Preface

Recipe Test Scripts To help you learn more effectively, this book has a dedicated site1 that contains the recipe test scripts and related resources. As the old saying goes, “There’s more than one way to skin a cat.” When it comes to achieving the desired testing outcomes, there are often multiple approaches. The recipe test scripts in this book are written for simplicity, so there is always room for improvement. But for many, understanding the solution quickly and getting the job done are probably more important. If you have a better and simpler way, please let us know. All recipe test scripts are Selenium WebDriver 4 compliant and can be executed on Chrome, Firefox, and Edge on multiple platforms.

Send Us Feedback We appreciate your comments, suggestions, and reports on errors in the book and the recipe test scripts. You may submit your feedback on the book site. Courtney Zhan and Zhimin Zhan September 2023

 http://zhimin.com/books/selenium-recipes-csharp

1

xxi

CHAPTER 1

Introduction Selenium WebDriver is a free and open-source library for automating web applications. Basically, you can use it to drive web apps in a browser and for testing purposes.

Selenium WebDriver Selenium WebDriver is a merger of Selenium v1 and another automation framework called WebDriver led by Simon Stewart at Google. (Simon later joined Facebook and Apple.) Selenium 2.0 was released in July 2011. We use “Selenium,” “WebDriver,” and “Selenium WebDriver” interchangeably in this book. Selenium v3 and v4 were released in October of 2016 and October of 2021, respectively.

Selenium Language Bindings Selenium scripts can be written in multiple programming languages such as Java, C#, JavaScript, Python, and Ruby (the core ones). All examples in this book are written in Selenium with C# binding. As you will see in the examples below, the use of Selenium in different bindings is very similar. Once you master one, you can apply it to others quite easily. Take a look at a simple Selenium test script in four different language bindings: C#, Java, Python, and Ruby.

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_1

1

Chapter 1

Introduction

C#: using using using using

System; Microsoft.VisualStudio.TestTools.UnitTesting; OpenQA.Selenium; OpenQA.Selenium.Chrome;

class GoogleSearch {   static void Main()   {     // Create a new instance of the driver     IWebDriver driver = new ChromeDriver();     // And now use this to visit Google     driver.Navigate().GoToUrl("http://www.google.com");     // Find the text input element by its name     IWebElement query = driver.FindElement(By.Name("q"));     // Enter something to search for     query.SendKeys("Hello Selenium WebDriver!");     // Submit the form based on an element in the form     query.Submit();     // Check the title of the page     Console.WriteLine(driver.Title);   } } Java: import import import import 2

org.openqa.selenium.By; org.openqa.selenium.WebDriver; org.openqa.selenium.WebElement; org.openqa.selenium.firefox.FirefoxDriver;

Chapter 1

Introduction

public class GoogleSearch  {   public static void main(String[] args) {     WebDriver driver = new FirefoxDriver();     driver.get("http://www.google.com");     WebElement element = driver.findElement(By.name("q"));     element.sendKeys("Hello Selenium WebDriver!");     element.submit();     System.out.println("Page title is: " + driver.getTitle());   } } Python: from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.google.com") elem = driver.find_element_by_name("q") elem.send_keys("Hello WebDriver!") elem.submit() print(driver.title) Ruby: require "selenium-webdriver" driver = Selenium::WebDriver.for :chrome driver.navigate.to "http://www.google.com" element = driver.find_element(:name, 'q') element.send_keys "Hello Selenium WebDriver!" element.submit puts driver.title

3

Chapter 1

Introduction

 et Up the Development Environment S Visual Studio Code Both the free Visual Studio Code (VS Code, for short) and the traditional Visual Studio IDE (Community or Enterprise edition) can be used to develop and execute C# Selenium tests. VS Code is a popular, if not the most popular, code editor and is more lightweight than the Visual Studio IDE. The Visual Studio IDE is better suited for development work. If you are only doing testing, VS Code is more than enough. We will use VS Code in this book. If you would like to use Visual Studio IDE, see the Appendix for setup instructions.

Prerequisites •

Download and install VS Code.



A browser, such as Chrome or Firefox



Have the browser’s driver (such as the ChromeDriver executable) included in the PATH.

Set Up Visual Studio Code 1. Install the .NET SDK. Download it from https://dotnet.microsoft.com/ en-us/download/dotnet/7.01. Note that .NET SDK now works on all major platforms. We chose macOS X64 since we were using a Mac. Obviously, it works on Windows too.

 https://dotnet.microsoft.com/en-us/download/dotnet/7.0

1

4

Chapter 1

Introduction

Run the installer (Figure 1-1).

Figure 1-1.  .NET SDK Installer screenshot Verify it was installed correctly by running this code in a new terminal window: $ dotnet --version 7.0.202 2. Install the C# and NuGet package manager extensions into VS Code. Click the Extensions tab on the left pane in VS Code (or use Command + Shift + X on a Mac or Control + Shift + X on Windows). Search for C# in the Extension Marketplace. This will add support for C# in VS Code. Install it (Figure 1-2).

Figure 1-2.  Visual Studio Code’s C# extension 5

Chapter 1

Introduction

Repeat the same process for the NuGet Package Manager, which helps to add libraries, such as Selenium WebDriver, to test projects (Figure 1-3).

Figure 1-3.  Visual Studio Code’s NuGet Package Manager extension 3. Create a MSTest project folder. The MSTest framework is a test framework which is included by default with Microsoft Visual Studio. From the terminal, run the following command to create a project named HelloSeleniumTest: $ dotnet new mstest -o HelloSeleniumTest Sample output: The template "MSTest Test Project" was created successfully. Processing post-creation actions... Restoring /Users/courtney/tmp/ HelloSeleniumTest/HelloSeleniumTest.csproj: Determining projects to restore... Restored /Users/courtney/tmp/HelloSeleniumTest/ HelloSeleniumTest.csproj   (in 1.16 sec). Restore succeeded. 6

Chapter 1

Introduction

4. Open the newly created test project folder in VS Code. Run the (empty) test case by clicking the Run Test link, as indicated in Figure 1-4.

Figure 1-4.  How to run an empty test in Visual Studio Code There is an extra compilation step, which was taken care of by VS Code, for readers who have only used scripting languages, such as Ruby and Python, as C# is a compiled language. As you can see, HelloSeleniumTest.dll is the output of the compilation (a.k.a. build). This empty test case is meaningless, but if it passes, it means your MSTest setup (with SDK) is correct.

7

Chapter 1

Introduction

5. Add the Selenium WebDriver package to the test project. You want to run a Selenium C# test that drives a Chrome browser (on macOS). There are three prerequisites: •

The Chrome browser



The ChromeDriver for the matching browser version is in the PATH



The Selenium WebDriver library must be installed and configured in the test project.

The first two are generic and needed for any WebDriverpowered automation. The third one you can achieve with the NuGet package manager. Press Command + Shift + P on Mac (or Control + Shift + P on Windows) to start the Command Palette, type NuGet, and select the NuGet Package Manager: Add Package option, as shown in Figure 1-5.

Figure 1-5.  Executing NuGet Package Manager’s Add Package command in Visual Studio Code Then type in Selenium and press the Enter key (Figure 1-6).

8

Chapter 1

Introduction

Figure 1-6.  Adding the package Selenium.WebDriver in Visual Studio Code Press the Enter key to select Selenium.WebDriver. Then select the latest version (in our case, 4.8.2). See Figure 1-7.

Figure 1-7.  Selecting Selenium.WebDriver’s package version in Visual Studio Code After that, you will see popups at the right bottom of VS Code (Figure 1-8).

Figure 1-8.  Prompt to restore unresolved dependencies in Visual Studio Code Click the Restore button. 9

Chapter 1

Introduction

6. First Selenium C# test Add a new file, LoginTest.cs, to the project (in VS Code). Paste in this content: namespace HelloSeleniumTest; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; [TestClass] public class SeleniumLoginOkTest {     IWebDriver driver = null;     [TestMethod]     public void TestLoginOK()     {         driver = new ChromeDriver();         driver.Navigate().GoToUrl("http://travel. agileway.net");         driver.FindElement(By.Name("username")). SendKeys("agileway");         driver.FindElement(By.Name("password")). SendKeys("testwise");         driver.FindElement(By.Name("password")). Submit();         Assert.IsTrue(driver.PageSource. Contains("Signed in!"));         driver.Quit();     } } Run it (as shown earlier in Step 4). You will see a Chrome browser launch and log into the AgileTravel site.

10

Chapter 1

Introduction

Cross-Browser Testing One big advantage of Selenium WebDriver over other web automation frameworks, in our opinions, is that it supports all major web browsers: Chrome, Safari, Microsoft Edge, and Firefox. To be more precise, all major browser vendors support WebDriver, a W3C standard. The browser market nowadays is more diversified (based on the StatsCounter2, the usage shares in May 2023 for Chrome, Safari, Edge, and Firefox are 66.0%, 12.8%, 9.9%, and 5.3%, respectively). It is logical that all external facing web sites require serious cross-browser testing. Selenium is a natural choice for this purpose because it far exceeds other web automation tools and frameworks.

Chrome To run Selenium tests in Google Chrome, besides the Chrome browser itself, ChromeDriver3 needs to be installed. Visit https://chromedriver. chromium.org/downloads4 and choose a version that matches your browser (Figure 1-9).

 http://gs.statcounter.com/  http://chromedriver.storage.googleapis.com/ 4  https://chromedriver.chromium.org/downloads 2 3

11

Chapter 1

Introduction

Figure 1-9.  ChromeDriver downloads page Download the one for your target platform, unzip it, and put the chromedriver executable in your PATH. To verify the installation, open a command window, execute the command chromedriver –version. You should see something like Figure 1-10.

Figure 1-10.  Verifying the ChromeDriver version via the command line The following code launches a new Chrome browser window and closes it one second later: namespace HelloSeleniumTest; using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; public class HelloSelenium {   public static void main(String[] args) {     IWebDriver driver = new ChromeDriver();     System.Threading.Thread.Sleep(1000);

12

Chapter 1

Introduction

    driver.Quit();   } } C# beginners might be confused/frustrated when running this simple code snippet, with issues related to “project,” “namespace,” and “compiling.” Don't worry. We will cover the test execution shortly. For readers who have used a good scripting language such as Ruby: Yes, test automation with Ruby is a lot easier.

F irefox Selenium for Firefox requires geckodriver5. The following test script will open a web site in a new Firefox window: using OpenQA.Selenium.Firefox; // ... IWebDriver driver = new FirefoxDriver();

S  afari Safari is the default macOS web browser. While Safari supports WebDriver out of the box, you will need to do minor setup to enable automation. 1. Enable Developer Tools in Safari. Open Safari’s Preferences (Safari ➤ Settings on the top bar or Command + ,). In the Advanced tab, check the “Show Develop menu in menu bar” box. See Figure 1-11.

 https://github.com/mozilla/geckodriver

5

13

Chapter 1

Introduction

Figure 1-11.  Enabling the Develop menu in Safari’s settings You should see the Develop menu in the top bar of Safari now. 2. Enable Remote Automation in Safari. Under the Develop menu tab, check the “Allow Remote Automation” option (Figure 1-12).

Figure 1-12.  Enabling Remote Automation in Safari’s menu bar using OpenQA.Selenium.Safari; // ... IWebDriver driver = new SafariDriver(); 14

Chapter 1

Introduction

E dge Edge is Microsoft’s new default web browser on Windows 10+. The current Edge is based on Chromium (the legacy version was deprecated in March 2021). To drive Edge with WebDriver, you need Microsoft Edge WebDriver6. The installation process is the same as ChromeDriver, and the file is msedgedriver.exe. using System.IO; using OpenQA.Selenium.Edge; // ... IWebDriver driver = new EdgeDriver();

I nternet Explorer Internet Explorer has retired. In a rare case that you need to test a certain website against IE, it is still possible. Selenium requires IEDriverServer to drive IE browser. Its installation process is very similar to ChromeDriver. IEDriverServer is available at https://www.selenium.dev/downloads7. Choose the right one based on your Windows version (32- or 64-bit). using OpenQA.Selenium.IE; //... IWebDriver driver = new InternetExplorerDriver();

Visual Studio Unit Testing Framework The script examples above drive browsers. Strictly speaking, they are not tests (Selenium is an automation framework). To make the effective use of Selenium scripts for testing, you need to put them in a test framework that  https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/  www.selenium.dev/downloads

6 7

15

Chapter 1

Introduction

defines test structures and provides assertions (performing checks in test scripts). Here is an example using Visual Studio Unit Testing Framework: using using using using using using using using using

System; Microsoft.VisualStudio.TestTools.UnitTesting; OpenQA.Selenium; OpenQA.Selenium.Firefox; OpenQA.Selenium.IE; OpenQA.Selenium.Chrome; OpenQA.Selenium.Edge; OpenQA.Selenium.Safari; System.Collections.ObjectModel;

[TestClass] public class GoogleSearchDifferentBrowsersTest {     [TestMethod]     public void TestInIE() {         IWebDriver driver = new InternetExplorerDriver();         driver.Navigate().GoToUrl("https://agileway.com. au/demo");         System.Threading.Thread.Sleep(1000);         driver.Quit();     }     [TestMethod]     public void TestInFirefox() {         IWebDriver driver = new FirefoxDriver();         driver.Navigate().GoToUrl("https://agileway.com. au/demo");         System.Threading.Thread.Sleep(1000);         driver.Quit();     }

16

Chapter 1

Introduction

    [TestMethod]     public void TestInChrome() {         IWebDriver driver = new ChromeDriver();         driver.Navigate().GoToUrl("https://agileway.com. au/demo");         System.Threading.Thread.Sleep(1000);         driver.Quit();     }     [TestMethod]     public void TestInSafari() {         IWebDriver driver = new SafariDriver();         driver.Navigate().GoToUrl("https://agileway.com. au/demo");         System.Threading.Thread.Sleep(1000);         driver.Quit();     }     [TestMethod]     public void TestInEdge() {         // Default option, MicrosoftWebDriver.exe must be in PATH         IWebDriver driver = new EdgeDriver();         driver.Navigate().GoToUrl("https://agileway.com. au/demo");         System.Threading.Thread.Sleep(1000);         driver.Quit();     } }

17

Chapter 1

Introduction

[TestMethod] annotates a test case below, in a format of TestCapitalCase. You will find more about VS Unit Test Framework from its home page8. However, we honestly don’t think it is necessary. The part used for test scripts is not much and is quite intuitive. After studying and trying out some examples, you will be quite comfortable with it.

Visual Studio Unit Testing Framework Fixtures If you worked with xUnit before, you must know the setUp() and tearDown() fixtures, which are called before or after every test. In VS Unit Test Framework, by using annotations ([ClassInitialize], [TestInitialize], [TestCleanup], and [ClassCleanup]), you can choose the name for the fixtures. Here is an example: [ClassInitialize] public static void BeforeAll() {    // run before all test cases } [TestInitialize] public void Before() {    // run before each test case } [TestMethod] public void TestCase1() {    // one test case }

 http://msdn.microsoft.com/en-us/library/ms243147

8

18

Chapter 1

Introduction

[TestMethod] public void TestCase2() {    // another test case } [TestCleanup] public void After() {    // run after each test case } [ClassCleanup] public static void AfterAll() {    // run after all test cases, typically, close browser } Please note that [ClassCleanup] does not guarantee all tests from one test class have been executed before a new test class is initialized. What does this mean for your execution? If you run one test case or all test cases in a single test class, [ClassCleanup] is invoked, it will run as you expected. However, when you run several test classes in one go, when [ClassCleanup] is invoked, it is nondeterministic (which could lead to many opened browser windows). This is not good. Read this MSDN blog post for explanation: “ClassCleanup May Run Later Than You Think.”9

Alternative Framework: NUnit NUnit10, inspired by JUnit, is an open-source unit testing framework for Microsoft .NET. If you have used JUnit before, you will find similarities in NUnit. Compared to VSTest, NUnit is more flexible for executing tests  http://blogs.msdn.com/b/ploeh/archive/2007/01/06/classcleanupmayrun laterthanyouthink.aspx 10  http://nunit.org/ 9

19

Chapter 1

Introduction

from the command line with JUnit style test reports (which can be easily integrated with CI Servers). However, you will need to spend some effort getting NUnit to work with Visual Studio.

Run Recipe Scripts Test scripts for all recipes can be downloaded from this book’s site. They are all in the ready-to-run state. We include the target web pages as well as the Selenium test scripts. There are two kinds of target web pages: local HTML files and web pages on a live site. Running the tests written for live sites requires an Internet connection.

Run Test(s) in VS Code The most convenient way to run one test case or a test suite is to do it in an IDE. When you have a large number of test cases, the most effective way to run all tests is done by a continuous testing process, which we cover in a later chapter.

Find the Test Case You can locate the recipe either by following the chapter or searching by name. There can be hundreds of test cases in one test project. Being able to quickly navigate to the test case is important when your test suite is large. Visual Studio Code has a Navigate To Symbol command; its default keyboard shortcut is Command + T on Mac (or Control + T on Windows). See Figure 1-13.

20

Chapter 1

Introduction

Figure 1-13.  Visual Studio Code’s Navigate To Symbol search box A pop-up window lists all artifacts (test methods and classes) in the project for your selection. The search starts as soon as you type, as you can see in Figure 1-14.

Figure 1-14.  Visual Studio Code’s Navigate To Symbol search box jumping to a specific test

Run an Individual Test Case Under the [TestMethod] annotation, there is an option called Run Test. Click this button to run the single test case (see Figure 1-15).

21

Chapter 1

Introduction

Figure 1-15.  The Run Test button in Visual Studio Code Figure 1-16 shows a screenshot of the execution panel where one test case has passed.

Figure 1-16.  Visual Studio Code execution panel after executing a test

Run All Test Cases in a Test Script File You can also run all test cases in the currently opened test script file by clicking the Run All Tests button under the [TestClass] annotation, as shown in Figure 1-17.

Figure 1-17.  The Run All Tests (in the current file) button in Visual Studio Code

22

Chapter 1

Introduction

Figure 1-18 is a screenshot of the execution panel where all test cases in a test script file have passed.

Figure 1-18.  Visual Studio Code execution panel after executing tests

Run Tests from the Command Line One key advantage of open-source test automation frameworks, such as Selenium, is FREEDOM. You can edit the test scripts in any text editor and run them from a command line. Here are two reasons why a test automation engineer must know how to execute tests from command line: •

You can eliminate issues from tools.



It’s required for continuous testing because the CT server invokes test executions this way.

To run a C# class, you need to compile it first (if you’re using an IDE, this is usually done automatically before execution). 1. Open a command prompt. You can use the standard command window (cmd. exe) or PowerShell. Also, most IDEs (including VS Code) provide an interface for the terminal. In VS code, you jump to the terminal with Control + `. 2. Change directory to your solution/working folder. cd C:\books\SeleniumRecipes-C#\recipes\ SeleniumRecipes

23

Chapter 1

Introduction

3. Run all test cases using the dotnet test in one test script (class). Run all tests in a file with dotnet test --filter NameSpace.TestClass. dotnet test --filter SeleniumRecipes. GoogleSearchDifferentBrowsersTest 4. Run an individual test case. To only run a single test case, use NameSpace. TestClass.TestMethod instead. For example: dotnet test --filter ­SeleniumRecipes. GoogleSearchDifferentBrowsersTest.TestInFirefox To run multiple test cases, use double quotations around the filter and the OR operator (|) to separate test classes: dotnet test --filter "SeleniumRecipes. GoogleSearchDifferentBrowsersTest.TestInFirefox | SeleniumRecipes.GoogleSearchDifferentBrowsersTest.TestInChrome" Sample output:    Starting test execution, please wait...    A total of 1 test files matched the specified pattern.    Passed!  - Failed:  0, Passed:  2, Skipped:  0, Total:  2, Duration: 12 s     - SeleniumRecipesCSharp.dll (net7.0)

24

CHAPTER 2

Locating Web Elements As you may have already figured out, to drive an element in a page, you need to find it first. Selenium uses locators to find and match elements on web page. There are eight locators in Selenium, shown in Table 2-1.

Table 2-1.  Selenium Locators Table

Example

ID

FindElement(By.Id("user"))

Name

FindElement(By.Name("username"))

Link Text

FindElement(By.LinkText("Login"))

Partial Link Text

FindElement(By.PartialLinkText("Next"))

Xpath

FindElement(By.Xpath("//div[@id='login']/ input"))

Tag Name

FindElement(By.TagName("body"))

Class Name

FindElement(By.ClassName("table"))

CSS

FindElement(By.CssSelector, "#login > input[type='text']"))

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_2

25

Chapter 2

Locating Web Elements

Selenium WebDriver 4 (released in October 2021) added a new locator type called Relative, which we don’t find that useful, yet. We include one simple example use of one relative locator in this chapter and more in Chapter 25. You may use any one of the locators to narrow down the element you are looking for.

S  tart a Browser Testing websites starts with a browser. static WebDriver driver = new ChromeDriver(); driver.Navigate().GoToUrl("https://agileway.com.au/demo") // or driver.Url = "https://agileway.com.au/demo"; Use ChromeDriver, SafariDriver, EdgeDriver, and FirefoxDriver for testing in Chrome, Safari, Edge, and Firefox, respectively.

Note: Test Site We prepared the test pages for the recipes. You can download them (in a zip file) from the book’s site. Unzip to a local directory and refer to test pages like this: siteUrl = "file:///Users/courtney/work/books/ SeleniumRecipes-C%23/site";driver.Navigate(). GoToUrl(siteUrl + "/button.html"); For beginners, we recommend closing the browser window at the end of a test class. driver.Quit(); 26

Chapter 2

Locating Web Elements

There is also driver.Close(), which closes the browser window with the focus. driver.Quit() closes all browser windows and ends the WebDriver session.

Inspect a Web Element in a Browser Note Every web page is set by its page source in HTML. A simple way to find the HTML fragment for a web element is to right-click the web element in a Chrome browser and select Inspect, as shown in Figure 2-1.

Figure 2-1.  Inspect mode on the Chrome browser Then choose an appropriate Selenium locator based on the HTML fragment. It is not hard, and you don’t even have to know HTML well. After some trial and error (like the recipes in this book), you will get it quite quickly. For example, for the text box in Figure 2-1, name="comment" can be used to locate it.

27

Chapter 2

Locating Web Elements

Find an Element by ID Using an ID is the easiest and safest way to locate an element in HTML, if it exists. For a web page that is W3C HTML compliant1, the IDs should be unique and identified in web controls. In comparison to texts, test scripts that use IDs are less prone to application changes (e.g., developers may decide to change the label, but are less likely to change the ID). driver.FindElement(By.Id("submit_btn")).Click();   // Button driver.FindElement(By.Id("cancel_link")).Click();   // Link driver.FindElement(By.Id("username")).SendKeys("agileway");   // Textfield driver.FindElement(By.Id("alert_div")).getText();  // HTML Div element

Find an Element by Name The name attributes are used in form controls such as text fields and radio buttons. The values of the name attributes are passed to the server when a form is submitted. In terms of the least likelihood of a change, the name attribute is probably as good as an ID. driver.Navigate().GoToUrl(TestHelper.SiteUrl() + "/ locators.html"); driver.FindElement(By.Name("comment")). SendKeys("Selenium Cool");

 https://zhiminzhan.medium.com/too-many-failed-javascript-testautomation-frameworks-fed006da05ec 1

28

Chapter 2

Locating Web Elements

Find an Element by Link Text For hyperlinks only, using a link’s text is probably the most direct way to click a link because it is what you see on the page. driver.FindElement(By.LinkText("Cancel")).Click();

Find an Element by Partial Link Text Selenium allows you to identify a hyperlink control with partial text. This can be quite useful when the text is dynamically generated. In other words, the text on one web page might be different on your next visit. You might be able to use the common text shared by these dynamically generated link texts to identify them. // will click the "Cancel" link driver.FindElement(By.PartialLinkText("ance")).Click();

Find an Element by XPath XPath, the XML Path Language, is a query language for selecting nodes from an XML document. When a browser renders a web page, it parses it into a DOM tree or similar. XPath can be used to refer to a certain node in the DOM tree. If this sounds a little too technical for you, don’t worry; just remember that XPath is the most powerful way to find a specific web control in HTML. // clicking the checkbox under 'div2' container driver.FindElement(By.XPath("//*[@id='div2']/input[@type= 'checkbox']")).Click(); Some testers feel intimidated by the complexity of XPath. However, in practice, there is only a limited scope of XPath to master for testers.

29

Chapter 2

Locating Web Elements

AVOID USING COPIED XPATH FROM A BROWSER’S DEVELOPER TOOL The browser’s developer tool (right-click to select the Inspect element to show it) is very useful for identifying a web element in a web page. You may get the XPath of a web element there, as shown in Figure 2-2 (in Chrome).

Figure 2-2.  Copying the XPath of an element The copied XPath for the second Click here link in the example is as follows: //*[@id="container"]/div[3]/div[2]/a It works. However, we do not recommend this approach because the test script is fragile. If a developer later adds another div under , the copied XPath will no longer be correct for the element, while //div[contains(text(), "Second")]/ a[text()="Click here"] still works. In summary, XPath is a very powerful way to locate web elements when Id, Name, or LinkText are not applicable. Try to use an XPath expression that is less vulnerable to structure changes around the web element.

30

Chapter 2

Locating Web Elements

Find an Element by a Tag Name There are a limited set of tag names in HTML. In other words, many elements share the same tag names on a web page. You normally don’t use the tag_name locator by itself to locate an element. You often use it with others in chained locators (see the section below). However, there is an exception: driver.FindElement(By.TagName("body")).Text; The above test statement returns the text view of a web page. This is a very useful one because Selenium WebDriver does not have a built-in method to return the text of a web page.

Find an Element by Class The class attribute of an HTML element is used for styling. It can also be used for identifying elements. Commonly, an HTML element’s class attribute has multiple values, like so: Cancel Submit You may use any one of them. driver.FindElement(By.ClassName("btn-primary")).Click(); // Submit button driver.FindElement(By.ClassName("btn")). Click();                  // Cancel link // the below will return error "Compound class names not permitted" // driver.FindElement((By.ClassName("btn btn-deault btn-­ primary")).Click();

31

Chapter 2

Locating Web Elements

The ClassName locator is convenient for testing JavaScript/CSS libraries (such as TinyMCE), which typically use a set of defined class names. // inline editing driver.FindElement(By.Id("client_notes")).Click(); System.Threading.Thread.Sleep(500); driver.FindElement(By.ClassName("editable-textarea")). SendKeys("inline notes"); System.Threading.Thread.Sleep(500); driver.FindElement(By.ClassName("editable-submit")).Click();

Find an Element by CSS Selector You may also use the CSS path to locate a web element. driver.FindElement(By.CssSelector("#div2 > input[type='checkbox']")).Click(); However, the use of a CSS selector is generally more prone to structure changes of a web page.

Find an Element by Relative Locators The purpose of relative locators is to find a specific element(s) regarding the position of another element. There was hype about relative locators (first introduced in Sahi Pro). However, we have reservations for two reasons:

32



We rarely have need for relative locators, so XPath is fine.



The proximity check (used in relative locators) does not always work as expected.

Chapter 2

Locating Web Elements

Here are quotes from Simon Stewart’s Selenium 4 master course on TestGuild2: •

“The way that proximity works, things are looking like they should be found, but sometimes aren’t.”



“Be aware that relative locators are very sensitive to page layout.”

Here is a simple example: using static OpenQA.Selenium.RelativeBy; IWebElement submitBtn = driver.FindElement(By. ClassName("btn")); IWebElement textField = driver.FindElement(RelativeBy. WithLocator(By.Name("comment")).Above(submitBtn)); You will find more examples of using this new relative locators in Chapter 25.

Chain FindElement to Find Child Elements For a page containing more than one element with the same attributes, like the following one, you can use the XPath locator.

   Same checkbox in Div 1

   Same checkbox in Div 2

 https://testguild.com/course/relative-locators

2

33

Chapter 2

Locating Web Elements

There is another way: you can chain several FindElement instances to find a child element. driver.FindElement(By.Id("div2")).FindElement(By.Name("same")). Click();

Find Multiple Elements As its name suggests, FindElements returns a list of matched elements back. Its syntax is the same as FindElement, so you can use any of the eight locators. The following test statement will find two checkboxes under div#container and click the second one: ReadOnlyCollection checkbox_elems = driver. FindElements(By.XPath("//div[@id='container']//input [@type='checkbox']")); System.Console.WriteLine(checkbox_elems);    // => 2 checkbox_elems[1].Click(); Sometimes FindElement fails due to multiple matching elements on a page, which you were not aware of. FindElements will come in handy to find them out.

L ocate a Web Element That Disappears After Inspect Dynamic elements are generated on the fly and often do not exist in the static page source. The only practical way (we are aware of ) is to get them by inspecting the dynamic element directly. However, some web elements disappear after you select the Inspect menu item (via right-click) because their focus has been lost.

34

Chapter 2

Locating Web Elements

There are two workarounds: 1. Open the developer tools in Chrome and select the Elements tab, right-­click the parent node of the element you want to inspect, and in the contextual menu, click Break on ➤ Subtree modifications (Figure 2-3).

Figure 2-3.  Chrome’s “Break on subtree modifications” setting 2. One simple workaround (more like a hack, but it works) is to type the following in the browser console: setTimeout(() => { debugger; }, 5000) This gives you 5 seconds to check the element source before the debugger shows up. See Figure 2-4.

35

Chapter 2

Locating Web Elements

Figure 2-4.  Chrome’s browser debugger mode

36

CHAPTER 3

Hyperlink Hyperlinks (or links) are fundamental elements of web pages. As a matter of fact, hyperlinks make the World Wide Web possible. A sample link is shown in Figure 3-1 and then we show the HTML source.

Figure 3-1.  An example of a hyperlink HTML source: Recommend Selenium

Click a Link by Text Using text is probably the most direct way to click a link in Selenium, as it is what we see on the page. driver.FindElement(By.LinkText("Recommend Selenium")).Click(); Please note that using CSS (e.g. text-transform: uppercase;) might change the text view from its HTML source. In this case, use the text as you see.

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_3

37

Chapter 3

Hyperlink

Click a Link by ID driver.FindElement(By.Id("recommend_selenium_link")).Click(); Furthermore, if you are testing a web site with multiple languages, using IDs is probably the most favorable option. You do not want to write test scripts like the following: if (isItalian()) {   driver.FindElement(By.LinkText("Accedi")).Click(); } else if (isChinese()) { // a helper function determines the locale   driver.FindElement(By.LinkText, "登录").Click(); } else {   driver.FindElement(By.LinkText("Sign in")).Click(); }

Click a Link by Partial Text driver.FindElement(By.PartialLinkText("Recommend Seleni")). Click();

Click a Link by XPath This example finds a link with the text “Recommend Selenium” under a

tag: driver.FindElement(By.XPath("//p/a[text()='Recommend Selenium']")).Click();

38

Chapter 3

Hyperlink

You might say the example before (find by LinkText) is simpler and more intuitive. That’s correct, but let’s examine another example, shown in Figure 3-2.

Figure 3-2.  An example of hyperlinks On this page, there are two Click here links. HTML source:        First div     Click here           Second div     Click here    If the test requires you to click the second Click here link, the simple FindElement(By.LinkText("Click here")) won’t work (because it clicks the first one). Here is a way to accomplish this using XPath: driver.FindElement(By.XPath( "//div[contains(text(), \"Second\")]/a[text()=\"Click here\"]")).Click();

39

Chapter 3

Hyperlink

Click the Nth Link with Exact Same Label It is not uncommon that there are multiple links with exactly the same text. By default, Selenium will choose the first one. What if you want to click the second or Nth one? The web page shown in Figure 3-3 contains three Show Answer links.

Figure 3-3.  Three ‘Show Answer’ links with the same label To click the second one, Assert.IsTrue(driver.FindElements(By.LinkText("Show Answer")). Count == 2); ReadOnlyCollection links = driver.FindElements(By. LinkText("Show Answer")); links[1].Click(); // click the second one Assert.IsTrue(driver.PageSource.Contains("second link page")); // second link FindElements return a collection (also called an array by some) of web controls matching the criteria in the appearing order. Selenium (in fact, C#) uses 0-based indexing (i.e., the first one is 0).

Click the Nth Link by CSS Selector You may also use CSS selector to locate a web element. // Click the 3rd link directly in

tag driver.FindElement(By.CssSelector("p  > a:nth-child(3)")).Click();

40

Chapter 3

Hyperlink

However, generally speaking, the use of a stylesheet is more prone to changes.

Verify If a Link Is Present or Not Assert.IsTrue(driver.FindElement(By.LinkText("Recommend Selenium")).Displayed); Assert.IsTrue(driver.FindElement(By.Id("recommend_selenium_ link")).Displayed);

Get Link Data Attributes Once a web control is identified, you can get other attributes of the element. This is generally applicable to most of the controls. It’s typically used for assertion purposes. Assert.AreEqual(TestHelper.SiteUrl() + "/index.html", driver.FindElement(By.LinkText("Recommend Selenium")). GetAttribute("href") ); Assert.AreEqual("recommend_selenium_link", driver.FindElement(By.LinkText("Recommend Selenium")). GetAttribute("id")); Assert.AreEqual("Recommend Selenium", driver.FindElement(By.Id("recommend_selenium_link")).Text); Assert.AreEqual("a", driver.FindElement(By.Id("recommend_selenium_link")).TagName); Also, you can get the value of custom attributes of this element and its inline CSS style.

41

Chapter 3

Hyperlink

Assert.AreEqual("font-size: 14px;", driver.FindElement(By.Id("recommend_selenium_link")). GetAttribute("style")); //  Please note using attribute_value("style") won't work Assert.AreEqual("123", driver.FindElement(By.Id("recommend_selenium_link")). GetAttribute("data-id"));

Test If a Link Opens a New Browser Window Clicking the following link will open the linked URL in a new browser window or tab: Open new window While you could use the SwitchTo() method (see Chapter 10) to find the new browser window, it’s easier to perform all testing within one browser window. Here’s how: String currentUrl = driver.Url; String newWindowUrl =   driver.FindElement(By.LinkText("Open new window")). GetAttribute("href"); driver.Navigate().GoToUrl(newWindowUrl); driver.FindElement(By.Name("name")).SendKeys("sometext"); driver.Navigate().GoToUrl(currentUrl); // back In this test script, you use a local variable named currentUrl to store the current URL.

42

CHAPTER 4

Button Buttons can come in two forms: standard and submit buttons. Standard buttons are usually created by the button tag, whereas submit buttons are created by the input tag (normally within forms). See Figure 4-1.

Figure 4-1.  Button examples HTML source: Choose Selenium

  Yes   No/option>

The intention of the following test script is to select Yes in the dropdown list, but it is not aware that there is another checkbox control sharing the same name attribute: Selenium::WebDriver::Support::Select.new(driver.FindElement(By. Name("vip"))).SelectByText("Yes") Here is the error returned: 'ArgumentError: unexpected tag name "input"' The solution is quite obvious after knowing the cause: change the locators, such as FindElement. A quite common scenario is as follows, where a hidden element and a checkbox element share the same ID and NAME attributes:

          BuildWise     2010               TestWise     2007     

232

Chapter 23

Selenium 4

Script with relative (above): var start_cell = driver.FindElement(By.Id("sitewise")); var the_above = driver.FindElement(RelativeBy.WithLocator(By. TagName("span")).Above(start_cell)); Assert.AreEqual(the_above.Text, "BuildWise"); You can use FindElements here as well. In this case, the returned texts are ["BuildWise", "ClinicWise", ...]. ReadOnlyCollection above_cells = driver. FindElements(RelativeBy.WithLocator(By.TagName("span")). Above(start_cell)); Assert.AreEqual(3, above_cells.Count); var cell_texts = above_cells.Select(s => s.Text).ToArray(); CollectionAssert.AreEqual(new string[]{"BuildWise", "ClinicWise", "Test automation products only" }, cell_texts); Please note the use of CollectionAssert here. Regular Assert does not work for array content checks.

B  elow in a Table The below works the same way. Here is an example of the immature status of relative locators. Let’s say you pick the “2014” as the starting cell (3rd row and 2nd column). The expected cell below is “2007,” right? However, it is not the case. var start_cell = driver.FindElement(By.XPath("//tr[3]/td[2]")); Assert.AreEqual("2014", start_cell.Text);

233

Chapter 23

Selenium 4

var the_below = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).Below(start_cell)); Console.WriteLine("The below 2014 is "+the_below.Text); //=> 2018, exp: 2007 The returned text is “2018,” the one further below. Why? Frankly, we don’t know. But we came up with one explanation after the following experiment. This time, we chose “2007” (4th row and 2nd column). Instead of the tag td, we used the tag span inside the td. var span_cell = driver.FindElement(By.XPath("//tr[3]/td[2]/ span")); Assert.AreEqual("2014", start_cell.Text); the_below = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).Below(span_cell)); Assert.AreEqual("2007", the_below.Text); It worked. Some might think this is just a workaround, but the previous example is not correct. I agree. Remember, relative locators are still relatively new. While we are here, let’s find its other neighbors. var start_cell = driver.FindElement(By.XPath( "//tr[4]/td[2]/ span")); Assert.AreEqual("2007", start_cell.Text); var the_above = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).Above(start_cell)); var the_below = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).Below(start_cell)); var the_left = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).LeftOf(start_cell)); var the_right = driver.FindElement(RelativeBy.WithLocator(By. TagName("td")).RightOf(start_cell));

234

Chapter 23

Selenium 4

Assert.AreEqual("2014", the_above.Text); Assert.AreEqual("2018", the_below.Text); Assert.AreEqual("TestWise", the_left.Text); Assert.AreEqual("https://testwisely.com/testwise", the_ right.Text); XPath alternative: driver.FindElement(By.XPath("//table[@id='grid']//tr[4]/ td[2]")).Text; //=>2017 driver.FindElement(By.XPath("//table[@id='grid']//tr[3]/ td[2]")).Text; //=> 2014 (above) driver.FindElement(By.XPath("//table[@id='grid']//tr[5]/ td[2]")).Text; //=> 2018 (below) driver.FindElement(By.XPath("//table[@id='grid']//tr[4]/ td[1]")).Text; //=> TestWise (left) driver.FindElement(By.XPath("//table[@id='grid']//tr[4]/ td[3]")).Text; // => https://testwisely.com/testwise (right)

N  ear See Figure 23-8.

Figure 23-8.  The second page of search results. The current page (2) is highlighted next to the Prev and Next links

235

Chapter 23

Selenium 4

HTML: First   Prev 2 Next   Last

Script with relative (near): var start_cell = driver.FindElement(By.Id("current-page")); ReadOnlyCollection neighbours = driver. FindElements(RelativeBy.WithLocator(By.TagName("a")). Near(start_cell)); Assert.AreEqual(2, neighbours.Count); var cell_texts = neighbours.Select(s => s.Text).ToArray(); CollectionAssert.AreEqual(new string[]{"Prev", "Next"}, cell_texts); However, we got a “System.NullReferenceException: Object reference not set to an instance of an object” exception on neighbours.Count. A similar test script in Ruby works fine. You can define the “nearness” by providing distance constraints. neighbours = driver.FindElements(RelativeBy.WithLocator(By. TagName("a")).Near(start_cell, 145)); cell_texts = neighbours.Select(s => s.Text).ToArray(); CollectionAssert.AreEqual(new string[]{"Prev", "Next", "First"}, cell_texts); // increase more distance, this time the above links neighbours = driver.FindElements(RelativeBy.WithLocator(By. TagName("a")).Near(start_cell, 208)); cell_texts = neighbours.Select(s => s.Text).ToArray(); CollectionAssert.AreEqual(new string[]{"Prev", "Next", "First", 236

Chapter 23

Selenium 4

    "https://whenwise.com", "http://sitewisecms.com",     "https://testwisely.com/testwise"}, cell_texts); Assert.IsFalse(cell_texts.Contains("Last"));

S  ummary Personally, we will not use relative locators in our test scripts, at least not yet. As you have seen in the above examples, the proximity calculation is not always accurate. Simon Stewart, Selenium project lead, shared the same view in his Selenium 4 Master Class4 as well.

 https://testguild.com/course/relative-locators/

4

237

CHAPTER 24

Selenium DevTools Google Chrome’s DevTools make use of a protocol called the Chrome DevTools Protocol (abbreviated as CDP) for developers to debug web apps and analyze the performance of web pages. As the name suggests, CDP is not designed for testing. Additionally, the stability of its API can vary, as its capabilities are closely tied to the specific version of the Chrome browser in use. WebDriver BiDi is a browser automation protocol under development, aiming to combine both the classic WebDriver and CDP for testing purposes. What does WebDriver BiDi mean for web automation engineers? You may use it to access the low-level control of the browser. Selenium v4 released with DevTools support is known as Selenium DevTools. Considering the previously mentioned issues with CDP, it’s advisable to use it cautiously. To use CDP in your script, you need to import its libraries first. Please note the version number here. using OpenQA.Selenium.Chrome; using OpenQA.Selenium.DevTools; // Replace the version to match the Chrome version using OpenQA.Selenium.DevTools.V115.Emulation; using OpenQA.Selenium.DevTools.V115.Network; using OpenQA.Selenium.DevTools.V115.Browser;

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_24

239

Chapter 24

Selenium DevTools

CDP functionalities are categorized into various groups, including Emulation, Network, Browser, and several others. When it comes to managing and adjusting the behaviors of the browser, CDP wields considerable power. An important note: Many CDP features are experimental (i.e., not fully implemented or possibly unstable). So keep that in mind. We suggest adhering to Selenium WebDriver as the primary choice and reserving CDP as a last resort. In this chapter, we’ll present a small selection of CDP examples. Once you have mastered the concepts and syntax, you can explore and utilize an extensive range of CDP features1.

Emulate Browser Crash To begin, let’s explore a straightforward task: causing the browser to crash. To be candid, we’re uncertain about the practical application of this within the realm of test automation; we selected this example due to its simplicity and ease of comprehension. using OpenQA.Selenium.Chrome; using OpenQA.Selenium.DevTools; // ... [TestMethod]   public void TestBrowserCrash() {     IDevTools devTools = driver as IDevTools;     DevToolsSession devToolsSession = devTools. GetDevToolsSession();

 www.selenium.dev/documentation/webdriver/bidirectional/ chrome_devtools/ 1

240

Chapter 24

Selenium DevTools

    var domains = ­devToolsSession.GetVersionSpecificDomains ();     domains.Browser.Crash();     System.Console.WriteLine("Still can reach here, browser is gone");   } As you can see, the script syntax is quite complex. Experienced testers will be concerned about the CDP version number being hardcoded in OpenQA.Selenium.DevTools.V115. We don’t like that either. Having said that, it seems to only affect C# as Selenium DevTools in Ruby offers a much cleaner syntax. require "selenium/devtools" #... driver.devtools.browser.crash There is no fixed version number here, which is better. The reason we included the Ruby version here is not for syntax comparison. Rather, it’s to show there can be a difference in execution too. For the Ruby version, after the browser crashes as instructed, it hangs for 30 seconds and then gets a timeout error. There is no such behavior for the C# version.

CDP Send Command CDP controls the Chrome browser via a set of commands, which are listed at chromedevtools.github.io2 (a lot of them).

 https://chromedevtools.github.io/devtools-protocol/tot/

2

241

Chapter 24

Selenium DevTools

The DevToolsSessionDomains.Browser.Crash you saw earlier is just a C# interface to send the Browser.crash CDP command. In theory, you can just send those commands directly. In fact, that’s our preferred approach when scripting in Ruby.     begin       Timeout::timeout(3) {         driver.devtools.send_cmd("Browser.crash")       }     rescue Timeout::Error     end However, we could not find the equivalent C# examples. This is a common challenge when working with CDP; its documentation is somewhat limited. If any readers are aware of such examples, please do let us know. Thanks in advance.

Emulate GEO Location The following test script emulates a Chrome browser session with its geolocation as the Sydney Opera House: [TestMethod] public void TestGeoLocation() {   IDevTools devTools = driver as IDevTools;   DevToolsSession devToolsSession = devTools. GetDevToolsSession();   var geoLocationOverrideCommandSettings = new SetGeolocationOverrideCommandSettings();   // set to Sydney Opera House   geoLocationOverrideCommandSettings.Latitude = -33.856159;   geoLocationOverrideCommandSettings.Longitude = 151.215256;   geoLocationOverrideCommandSettings.Accuracy = 1; 242

Chapter 24

Selenium DevTools

  var domains = ­devToolsSession.GetVersionSpecificDomains();   domains.Emulation.SetGeolocationOverride(geoLocationOverride CommandSettings);   driver.Navigate().GoToUrl("https://my-location.org/");   System.Threading.Thread.Sleep(500);   Assert.IsTrue(driver.FindElement(By.TagName("body")).Text. Contains("Sydney Opera House")); } As you can see, the syntax is less desirable than the JavaScript version in Chapter 18.

E mulate Locale The following script alters the locale (language settings) of the Chrome browser: driver.Url = "https://www.localeplanet.com/support/ browser.html"; Assert.IsFalse(driver.FindElement(By.TagName("body")).Text. Contains("オーストラリア東部標準時")); IDevTools devTools = driver as IDevTools; DevToolsSession devToolsSession = devTools. GetDevToolsSession(); SetLocaleOverrideCommandSettings localeSettings = new SetLocaleOverrideCommandSettings(); localeSettings.Locale = "ja_JP"; var domains = devToolsSession.GetVersionSpecificDomains();

243

Chapter 24

Selenium DevTools

domains.Emulation.SetLocaleOverride(localeSettings); ­driver.Navigate().GoToUrl("https://www.localeplanet.com/ support/browser.html"); Assert.IsTrue(driver.FindElement(By.TagName("body")).Text. Contains("オーストラリア東部標準時")); We included two assertions (before and after) to verify the emulation actually works, which is a good test design practice.

E mulate Timezone The following script sets a Chrome browser’s time zone for testing purposes: IDevTools devTools = driver as IDevTools; DevToolsSession devToolsSession = devTools. GetDevToolsSession(); var timezoneSettings = new SetTimezoneOverrideCommandSettings(); timezoneSettings.TimezoneId = "Asia/Tokyo"; var domains = devToolsSession.GetVersionSpecificDomains (); domains.Emulation.SetTimezoneOverride(timezoneSettings); driver.Navigate().GoToUrl("https://whatismytimezone.com"); System.Threading.Thread.Sleep(1500); Assert.IsTrue(driver.FindElement(By.TagName("body")).Text. Contains("Japan Standard Time"));

N  etwork Interception The following script visits a web page but does not load images and styles:

244

Chapter 24

Selenium DevTools

var domains = devToolsSession.GetVersionSpecificDomains (); domains.Network.Enable(new Network.EnableCommandSettings()); domains.Network.SetBlockedURLs(new Network. SetBlockedURLsCommandSettings() {     Urls = new string[] { "*://*/*.css", "*://*/*.jpg", "*://*/*.png" } }); driver.Url = "https://whenwise.agileway.net"; System.Console.WriteLine("Page is shown, but no image and styles");

N  etwork Latency First, a helpful tip. We don’t like to use the CDP version number, so making it appear multiple times is even worse. A simple way to create aliases is as follows: // Replace the version to match the Chrome version using Network = OpenQA.Selenium.DevTools.V115.Network; The following test script verifies that the loading of a web page is indeed slower after adding a network latency: using System.Diagnostics; // to use StopWatch // ... [TestMethod] public void TestNetworkLatency() {   // warm up the server   driver.Navigate().GoToUrl("https://travel.agileway.net");

245

Chapter 24

Selenium DevTools

  Stopwatch sw1 = new Stopwatch();   sw1.Start();   driver.Navigate().GoToUrl("https://travel.agileway.net");   sw1.Stop();   long timing1 =  sw1.ElapsedMilliseconds;   Console.WriteLine("Normal operation: " + timing1 + " (ns)");   var domains = devToolsSession.GetVersionSpecificDomains();   var networkSettings = new OpenQA.Selenium.DevTools.V115. Network.EmulateNetworkConditionsCommandSettings();   networkSettings.Latency = 3000;   domains.Network.EmulateNetworkConditions(networkSettings);   Stopwatch sw2 = new Stopwatch();   sw2.Start();   driver.Navigate().GoToUrl("https://travel.agileway.net");   sw2.Stop();   long timing2 =  sw2.ElapsedMilliseconds;   Console.WriteLine("With Latency operation: " + timing2 + " (ns)");   Assert.IsTrue( timing2 > 3000);   Assert.IsTrue( timing2 > timing1);   Assert.IsTrue( (timing2 - timing1) > 2000 ); // not set 3000 to allow some network variations } We used StopWatch.ElapsedMilliseconds for timing.

246

Chapter 24

Selenium DevTools

Security: Ignore CertificateErrors When you access a webpage with certificate errors, a warning page typically appears. You can bypass this warning by instructing CDP (Chrome DevTools Protocol) to ignore certificate errors. public void TestIgnoreCertificateErrors() {   driver.Url = "https://expired.badssl.com";   Assert.IsTrue(driver.FindElement(By.TagName("body")).Text. Contains("Your connection is not private"));   IDevTools devTools = driver as IDevTools;   DevToolsSession devToolsSession = devTools. GetDevToolsSession();   SetIgnoreCertificateErrorsCommandSettings securitySettings = new SetIgnoreCertificateErrorsCommandSettings();   securitySettings.Ignore = true;   var domains = devToolsSession.GetVersionSpecificDomains();   domains.Security.Enable();   domains.Security.SetIgnoreCertificateErrors(security Settings);   driver.Navigate().GoToUrl("https://expired.badssl.com");   Assert.IsFalse(driver.FindElement(By.TagName("body")).Text. Contains("Your connection is not private")); }

247

CHAPTER 25

Selenium Grid Quality assurance engineers develop Selenium WebDriver tests using a testing integrated development environment (IDE) and subsequently execute individual tests from within this IDE. Nonetheless, when it comes to running a substantial number of time-consuming UI tests, a dedicated tool designed for execution and feedback management is recommended. Selenium Grid serves as such a tool. It is offered by the core Selenium Team. Selenium Grid, formerly known as Selenium Remote Control (RC) Server, allows testers to write Selenium tests in their favorite language and execute them on another machine. The word “remote” means that the test scripts and the target browser may not be on the same machine. In this chapter, we will show you how to set up and run tests in Selenium Grid. Remote Selenium execution is composed of two pieces: a server and a client. •

Selenium server: A Java server that automatically launches, drives, and kills browsers, with the target browser installed on the machine



Client libraries: Test scripts in a language binding, such as Ruby, Java, C#, or Python

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_25

249

Chapter 25

Selenium Grid

Selenium Server Standalone Make sure you have Java Runtime (JRE) installed first. C# Engineers, we know how some of you feel about using a rival language. The Selenium Grid Server, to our knowledge, is only implemented in Java. This won’t affect running tests written in different languages though, which we will show you shortly. Download Selenium Server (selenium-server-{VERSION}.jar) from the Selenium download page1 and place it on the computer with the browser you want to run tests on. Then start the server (in standalone mode) with the following command: java -jar selenium-server-4.11.0.jar standalone --port 1234 Sample output when the server is ready: ... INFO [Standalone.execute] - Started Selenium Standalone 4.11.0 (revision 040bc5406b): http://192.168.1.27:1234

Execute Tests on a Remote Machine Prerequisites: •

Make sure the Selenium Server is up and running.



Note down the server machine’s IP address and its running port. If you have just started the server, this is printed on the last line.

 www.selenium.dev/downloads/

1

250

Chapter 25

Selenium Grid

Changing existing local Selenium tests (running on a local browser) to remote Selenium tests (running on a remote browser) is very easy. Just update the initialization of Selenium::WebDriver as follows: using OpenQA.Selenium.Remote; // ... ChromeOptions opts = new ChromeOptions(); IWebDriver driver = new RemoteWebDriver(new Uri("http:// localhost:1234"), opts); driver.Navigate().GoToUrl("https://whenwise.agileway.net"); Assert.AreEqual("WhenWise - Booking Made Easy", driver.Title); Just replace driver = new ChromeDriver(); with the two lines above.

Set Up Selenium Grid Selenium Grid allows you to run Selenium tests in parallel to cut down the execution time. Selenium Grid uses one hub and many nodes. 1. Start the hub. The hub receives the test requests and distributes them to the nodes. java -jar selenium-server-4.11.0.jar hub Sample output: INFO [Hub.execute] - Started Selenium Hub 4.11.0 (revision 040bc5406b): http://192.168.1.27:4444

251

Chapter 25

Selenium Grid

2. Start the nodes. A node runs tests that it gets from the hub. The following is a command to start a node (in this case, we ran the node on the same machine as the hub (macOS)): java -jar selenium-server-4.11.0.jar node --hub http://192.168.1.27:4444 Sample output: [NodeServer.createHandlers] - Reporting self as: http://192.168.1.27:5555 [NodeOptions.getSessionFactories] - Detected 12 available processors [NodeOptions.discoverDrivers] - Discovered 3 driver(s) [NodeOptions.report] - Adding Firefox for {"browserName": "firefox"} 12 times [NodeOptions.report] - Adding Chrome for {"browserName": "chrome"} 12 times [NodeOptions.report] - Adding Safari for {"browserName": "safari"} 1 times [Node.] - Binding additional locator mechanisms: name, id, relative [NodeServer$1.start] - Starting registration process for node id 4faa612e-... [NodeServer.execute] - Started Selenium node 4.0.0 : http://192.168.1.27:5555 [NodeServer$1.lambda$start$1] - Sending registration event... [NodeServer.lambda$createHandlers$2] - Node has been added

252

Chapter 25

Selenium Grid

You can run the same command on another machine (Windows) to add a new node to this grid system. java -jar C:\Users\CIO\Downloads\seleniumserver-4.11.0.jar node --hub http://192.168.1.27:4444 Please note the different node IP address in the output. [NodeServer.execute] - Started Selenium node 4.11.0 (revision 040bc5406b): http://192.168.1.224:5555 [NodeServer$1.lambda$start$1] - Sending registration event... [NodeServer.lambda$createHandlers$2] - Node has been added 3. View Grid. Visit http://192.168.1.27:4444/ui to see Grid’s web interface. See Figure 25-1.

253

Chapter 25

Selenium Grid

Figure 25-1.  Grid’s web interface As you can see, two nodes (one macOS and one Windows, with their IP addresses) are shown there.

Using Selenium Grid to Run Tests This is quite easy to do; you just change one line of your test script to point the RemoteWebDriver to the hub. public void TestSeleniumGridHubChrome() {     // server started with     //    java -jar selenium-server-4.11.0.jar hub     // and at least one grid node up running     ChromeOptions opts = new ChromeOptions();     IWebDriver driver = new RemoteWebDriver(new Uri("http://192.168.1.27:4444/"),       opts); 254

Chapter 25

Selenium Grid

    driver.Navigate().GoToUrl("https://whenwise.agileway.net");     Assert.AreEqual("WhenWise - Booking Made Easy", driver.Title);     driver.Quit(); } Then you run your test the way you usually do: in Visual Studio Code, from the command line, or in a Continuous Testing server (see the next chapter). The test will run on one of the nodes. The assignments can be viewed on Grid’s sessions page, as shown in Figure 25-2.

Figure 25-2.  Selenium Grid’s session page when there is one active session Astute readers may wonder how to manage the test executions in Selenium Grid. For example, the last run was on the Windows 10 node, so how can you make it run against Firefox on a macOS node? To do that, specify the PlatformName in the browser options. public void TestSeleniumGridHubFirefox() {   FirefoxOptions opts = new FirefoxOptions();   opts.PlatformName = "macOS";

255

Chapter 25

Selenium Grid

  IWebDriver driver = new RemoteWebDriver(new Uri("http://192.168.1.27:4444/"),      opts);   // ... } To be candid, our exposure to Selenium Grid is somewhat limited as we found Selenium Grid, while working, to be complex and yet lacking some key features for Continuous Testing (see below). We’ve come across a few instances of companies attempting to use Selenium Grid, but none of those endeavors proved successful. We have been using BuildWise2, a free and open-source Continuous Testing server, to manage Selenium test executions.

Issues with Selenium Grid From our experience, many projects are using Selenium Grid just for crossbrowser testing, rather than functional regression testing, which is far more challenging. I think the Selenium team also realized this. “Selenium Grid 4 is a fresh implementation and does not share the codebase the previous version had” (from the official documentation3). In our opinions, even with the improvements in v4, there are issues with Selenium Grid:

 https://agileway.com.au/buildwise  www.selenium.dev/documentation/grid/

2 3

256

Chapter 25

Selenium Grid



Complexity



For every Selenium Grid node, you need to configure the node either by specifying command line parameters or a JSON file. Check out the Grid Wiki page4 for details.



It is our understanding that after pushing the tests to the hub, the hub handles the rest based on the configuration. Our experience tells us that it is too good to be true.



Very limited control



Selenium Grid comes with a web accessible console, which in our view is very basic (even with Selenium Grid 4) in terms of viewing test execution status (such as output and history) and control test executions (such as cancellation and reassignment).



Lack of feedback



UI tests take time to execute. More tests mean longer execution time. Selenium Grid’s distribution model is to reduce that. Apart from the raw execution time, there is also the feedback time. The team would like to see the test results as soon as a test execution finishes on one node. Even better, when we pass the whole test suite to the hub, it will “intelligently” run new or last failed tests first. Selenium Grid, in our view, falls short on this.

 www.selenium.dev/documentation/grid/

4

257

Chapter 25



Selenium Grid

Lack of rerun In a perfect world, all tests execute as expected every single time. But in reality, there are so many factors that can affect the test execution: •

Test statements didn’t wait long enough for AJAX requests to complete (server on load)



Browser crashes (it happens)



Node runs out of disk space



Virus scanning process starts in the background



Windows self-installs an update





In this case, reassigning failed tests to another node could save a potential good run. Our point is we could quickly put together a demo with Selenium Grid running tests on different nodes (with different browsers), and the audience might be quite impressed. However, when you have a large number of UI tests, the game is totally different. The whole process needs to be simple, stable, flexible, and (very importantly) able to provide feedback quickly. In the true spirit of Agile, if there are tests failing, no code should be allowed to check in. This puts a lot of pressure on QA Engineers to maintain a highly-reliable and valid suite of automated UI tests, amid frequent application changes. Quite often, people associate Selenium Grid with distributed test execution and cross-browser testing. However, we believe this perception is inaccurate, as these two activities are separate and not inherently interconnected. Distributed test execution accelerates the testing process (potentially against a single browser type) whereas cross-browser testing ensures an application’s compatibility across various browsers.

258

Chapter 25

Selenium Grid

With the dominance of Chromium-based browsers like Google Chrome and Microsoft Edge in the desktop browser market share, the importance of cross-browser testing has decreased. For most software projects, performing End-to-End (E2E) tests exclusively on Chrome is generally adequate. This aligns with the current prevailing situation. Hence, the significance of Selenium Grid resides in its capability for distributed test execution. We strongly advocate that executing UI tests with feedback should be integrated into a robust CI process. Nevertheless, conventional CI servers like Jenkins are tailored for running unit and integration tests, making them less suitable for E2E UI tests. Although Selenium Grid is designed for executing Selenium tests, we’ve noticed that it’s seldom employed in software projects. One contributing factor is its significant divergence from CI servers, making it not aligned well with the CI/CD Pipeline concept. So, what is the solution for executing UI test suites then? We believe it is a form of Continuous Testing server, which is the topic of the next chapter.

259

CHAPTER 26

Continuous Testing End-to-end (E2E) test automation is only truly useful when we routinely execute the whole suite frequently to obtain rapid feedback. Obviously, it is not practical to execute the whole suite frequently in IDEs such as Visual Studio Code and TestWise. Instead, we run a suite of E2E tests regularly in a Continuous Testing server and make the test reports accessible by all team members. The term “Continuous Testing” (CT for short) comes from DevOps. According to Wikipedia, Continuous Testing is “the process of executing automated tests as part of the software delivery pipeline to obtain immediate feedback on the business risks associated with a software release candidate.” Our interpretation of CT: “Run automated end-to-end (UI) as regression testing, frequently on new builds. If all tests pass, the software is ready for production release. If there are test failures, the team must act quickly on the feedback.” This book contains more than 200 test recipes, essentially forming a comprehensive test suite. In this chapter, we will demonstrate how we frequently execute these tests. Figure 26-1 depicts the execution of all 214 recipe tests within a BuildWise CT Server.

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_26

261

Chapter 26

Continuous Testing

Figure 26-1.  BuildWise CT Server result for all recipes in this book With a click of a button (on BuildWise’s Web UI), we can get a test report in about 7 minutes.

Note  BuildWise CT server is 100% open-source, developed in Ruby by Zhimin Zhan. It won the Second Prize at 2018 International Ruby Award. BuildWise works on all major platforms. While we predominantly used macOS in earlier chapters, we will be demonstrating the setup process on Windows for this exercise, as it is generally considered to be a more demanding platform for server configurations. We recommend that you follow the instructions provided in this chapter to set up one BuildWise CT server on your computer.

262

Chapter 26

Continuous Testing

CI vs. CT Undoubtedly, some readers may be curious about the distinctions between Continuous Testing (CT) and Continuous Integration/ Continuous Delivery (CI/CD). The in-depth exploration of this topic goes beyond the scope of this book. Refer to Practical Continuous Testing by Zhimin Zhan for a comprehensive understanding. However, in this section, we provide a brief comparison. See Figure 26-2.

Figure 26-2.  Comparison table between Continuous Integration and Continuous Testing Figure 26-3 shows our preferred way to engage both CI and CT, with different focuses, within one team.

263

Chapter 26

Continuous Testing

Figure 26-3.  Diagram illustrating differences between CI and CT processes within a team. Software Development Engineers (SDEs) interact with the CI server, whereas Software Engineers in Test (SETs) run UI tests in the CT server for the whole team to view and access

Preparation Essentially, when it comes to testing, a CI or CT server initiates a process by running automated tests from the command line and subsequently displays the test results in its user interface. Therefore, your initial focus will be on understanding how to execute tests from the command line.

Source Control Test Scripts in Git Similar to how programmers store their code, automated test scripts also need to be managed in a version control system, such as Git. If you are not familiar with Git (for version control), you should become so; in fact, it is a must for CT. There are plenty of Git tutorials available online. We suggest

264

Chapter 26

Continuous Testing

using Git from the command line. Refer to the “10-Minute Guide to Git Version Control for Testers1 ” for a quick introduction. Get sample Selenium WebDriver test scripts and run them. See Figure 26-4. > cd c:\work > git clone https://github.com/testwisely/buildwise-samples.git

Figure 26-4.  Cloning a BuildWise sample test script repository from the command line The checked-out directory is C:\work\buildwise-samples. This folder contains a set of test projects for different frameworks; e2e-vstestselenium is the one for Selenium C#. We will refer to C:\work\buildwisesamples\e2e-vstest-selenium as %TEST_PROJECT.

Compile Test Scripts Start a command window and run the following command to get to the test project folder, which contains the xxx.csproj file: > cd %TEST_PROJECT% Compile the test scripts. We have a habit of cleaning first. > dotnet clean > dotet build

 https://zhiminzhan.medium.com/10-minutes-guide-to-git-versioncontrol-for-testers-f58e059bb5e7 1

265

Chapter 26

Continuous Testing

If successful, this will generate a %PROJECT_NAME%.dll file under ­bin\Debug\net7.0.

Execute a Test Script from the Command Line Run one test script with the test class name. > cd %TEST_PROJECT% > dotnet test bin\Debug\net7.0\e2e-vstest-selenium.dll --filter e2e_vstest_selenium.LoginTest You will see test execution in a new Chrome browser window (if not, make sure chromedriver is installed properly). Here is a sample output:   Determining projects to restore...   All projects are up-to-date for restore.   e2e-vstest-selenium -> ...\bin\Debug\net7.0\e2e-vstest-­ selenium.dll Test run for .../bin/Debug/net7.0/e2e-vstest-selenium.dll (.NETCoreApp,Version=v7.0) Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64) Copyright (c) Microsoft Corporation.  All rights reserved. Starting test execution, please wait... A total of 1 test files matched the specified pattern. Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 8 s   - e2e-vstest-selenium.dll (net7.0) You can also run from any folder if provided with an absolute path. > dotnet test %MY_TEST_PROJECT%\bin\Debug\net7.0\ SeleniumRecipesCSharp.dll --filter ­e2e_vstest_selenium. FlightTest 266

Chapter 26

Continuous Testing

Note  You may also execute C# tests using the vstest.console command. vstest.console SeleniumRecipes.dll /Tests:TestSendSpecialKeys, TestModalDialog

For more VSTest.Console options, visit Microsoft’s documentation.2

Execute a Test Script with JUnit XML Most (if not all) CI/CT servers analyze test results in the JUnit Report XML format. > dotnet test bin\Debug\net7.0\e2e-vstest-selenium.dll --filter e2e_vstest_selenium.LoginTest --logger "junit;LogFilePath=./ TestResults.xml" You will find a TestResults.xml file generated, with content like this:

                 

 https://msdn.microsoft.com/en-us/library/jj155796.aspx

2

267

Chapter 26

Continuous Testing

            

Set Up BuildWise Server The detailed instructions can be found on Courtney Zhan’s Medium blog, in the “Set up BuildWise CT Server Step by Step” article3. Here, we will be brief. 1. Install Ruby. It is quite easy to install Ruby on macOS and Linux using a package manager such as brew on macOS and apt-get on Linux. For the Windows platform, use RubyInstaller for Windows. We suggest Ruby + DevKit 3.2.2–1 (x64). Follow the on-screen instructions. 2. Download BuildWise Server and unzip it. Visit https://agileway.com.au/buildwise and download the server package (a zip file). Copy it to a folder, such as c:\agileway, right-click, and unzip it there. In this case, a BuildWise server was installed under c:\agileway\buildwise-2.3.5.

 https://courtneyzhan.medium.com/set-up-buildwise-ct-serverstep-by-step-98ef0c96702c 3

268

Chapter 26

Continuous Testing

3. Start up the BuildWise server. Install the libraries (called gems in Ruby) required for the BuildWise server. > cd C:\agileway\buildwise-2.3.5 > bundle This will take a few minutes. Once it is done, run bundle-startupdemo.bat (in the command window) to start the server. > cd C:\agileway\buildwise-2.3.5 > bundle-startup-demo.bat For the first time, you will see a fairly large chunk of text passing by (Figure 26-5). That’s for setting up the database (one time).

Figure 26-5.  BuildWise starting up for the first time after running bundle-startup-demo.bat When you see the text shown in Figure 26-6, the server is up and running.

Figure 26-6.  The BuildWise server has started up and is ready to use Open http://localhost:3618 in your browser. See Figure 26-7.

269

Chapter 26

Continuous Testing

Figure 26-7.  BuildWise’s homepage Log in as the default user ( admin / buildwise ).

Set Up a Build Project BuildWise uses the concept of a project to confine the settings and build activities on how you execute tests. Click the “Create a New Project” button. Fill in the settings as shown in Figure 26-8. Avoid using special characters in your project name/identifier (i.e. use Sharp instead of #).

270

Chapter 26

Continuous Testing

Figure 26-8.  BuildWise’s New Project page, with the settings for a C# project The important one is the working folder. Use the one (on your local disk) that you just checked out from the parent Git repository. You can ignore the “Test framework” dropdown for now. Click the Create button. Go to the project settings (by clicking the project name on the home page). Add a new step and name it Compile with the text -f e2e-vstest-­ selenium/Rakefile build. See Figure 26-9.

Figure 26-9.  Adding a compilation step in BuildWise’s build steps 271

Chapter 26

Continuous Testing

Then in the drop-down under Build Steps, select VSTest (C#). Then, save the project settings.

Run a Suite of C# tests in BuildWise Click the Build Now button to start a run in BuildWise. You will see several black windows pop up and go, which are the processes started by BuildWise to perform the following build steps: 1. Get the latest test scripts from the parent Git repository. 2. Compile the test scripts. 3. Execute each Selenium C# test script. After seeing the test execution (in the Chrome browser) complete, you will get a test report like the one shown in Figure 26-10.

Figure 26-10.  A BuildWise successful test execution report

272

Chapter 26

Continuous Testing

What’s the Magic? BuildWise embraces an open design. The user has maximum control via changing the Rakefile, the standard build file for Ruby. For more information on Continuous Testing, check out Zhimin Zhan’s book Practical Continuous Testing4.

 https://leanpub.com/practical-continuous-testing

4

273

CHAPTER 27

Case Studies You have seen over 200 recipes for individual web testing operations throughout this book so far. In this chapter, we will showcase a selection of complete test scenarios from my blog that apply the techniques you’ve acquired.

Verify Chart Generation Test Case: Verify a chart is generated successfully on a web page Charting is a common feature in modern web applications. Charting packages like HighCharts typically generate charts in the SVG (Scalable Vector Graphics) format. While we can visually confirm a generated chart manually, how can we accomplish this task in an automated test script?

Test Design 1. Verify the chart is present by checking the svg tag. 2. Save the chart to a file. 3. Verify the image file (in an automated test script). 4. Visually inspect the image file on the CT server.

© Courtney Zhan, Edited by Zhimin Zhan 2024 C. Zhan, Selenium WebDriver Recipes in C#, https://doi.org/10.1007/979-8-8688-0023-8_27

275

Chapter 27

Case Studies

T est Steps 1. Verify the chart exists. To confirm the chart’s presence, you must ascertain whether the designated div tag actually contains the chart. In HighCharts, you must create a div and give it an ID, and then HighCharts will render the chart there.

Here is a simple JavaScript example that typically generates a Highcharts chart in the #chartcontainer div: Highcharts.chart('chart-container', {     chart: {         type: 'area'     },     xAxis: {         categories: ['Jan', 'Feb', 'Mar']     },     series: [{         data: [29.9, 71.5, 106.4]     }] }); On loading the web page, you will notice that the chart is not there. This test script verifies this:

276

Chapter 27

Case Studies

driver.Navigate().GoToUrl(TestHelper.SiteUrl() + "/dynamic-­chart.html"); try {     driver.FindElement(By.TagName("svg"));     Assert.Fail("The SVG should not existed yet"); } catch (OpenQA.Selenium.NoSuchElementException e) {     Console.WriteLine("As expected"); } After clicking the Generate button, the above JavaScript will render a SVG chart. View the page source now. You still won’t see the tag, but you can find it via inspection (rightclick the page and select Inspect). The following test script verifies this: driver.FindElement(By.Id("gen-btn")).Click(); System.Threading.Thread.Sleep(1000); // load JS WebElement svg_elem = (WebElement) driver. FindElement(By.TagName("svg")); 2. Save your chart as an image file. This is the preparation step to verify the chart. Screenshot screenshot = svg_elem.GetScreenshot(); var savedChartPath = @"C:\temp\chart.png" screenshot.SaveAsFile(savedChartPath, ScreenshotImageFormat.Png); Assert.IsTrue(File.Exists(savedChartPath)); Please note that using the absolute path is not good, but it is OK for the initial attempt. A better way is to use a relative path to the test script.

277

Chapter 27

Case Studies

savedChartPath = Path.Combine(Environment. CurrentDirectory, "chart.png"); Since C# is a compiled language, having a direct relative path with the test script file is not feasible. Instead, you can utilize the current directory from which the test execution is initiated, commonly in the form of {PROJECT}/bin/Debug/net7.0. 3. Verify the image file. You saved the chart to a file named svg.png, but you can’t guarantee it is a valid image. The following test script tries to load the saved image file and verify it is a valid image. As you know (from the HTML), the chart height is 400 pixels. This is a sufficient criterion to check the saved image.

Note  System.Drawing.Image (adding System.Drawing. Common via NuGet) only works on the Windows platform, which is surprising. if (OperatingSystem.IsWindows()) {   System.Drawing.Image img = System.Drawing.Image. FromFile(savedChartPath);   Console.WriteLine("Width: " + img.Width + ", Height: " + img.Height);   Assert.AreEqual(400, img.Height); } 4. View the image file on the CT server.

278

Chapter 27

Case Studies

The automated test script is complete after Step 3. You still can do better by making the saved image file available to all team members. This can be beneficial if you want other team members, such as Business Analysts, to verify the chart contents. How can you achieve this? Run the test script, along with others, in a BuildWise CT server. In BuildWise, you may specify files or directories to archive under the project settings. See Figure 27-1.

Figure 27-1.  BuildWise’s setting to archive files After the build is completed, you can click the "Build artifacts" dropdown, and see the chart image link listed there. See Figure 27-2.

Figure 27-2.  Viewing the build artifacts in BuildWise inside the test results Once you click it, you can view the chart, straight from the CT server, in your browser! See Figure 27-3.

279

Chapter 27

Case Studies

Figure 27-3.  Viewing specific build artifact (chart.png) directly in BuildWise

T est Script // savedChartFilePath is defined in [ClassInitialize] [TestMethod] public void TestSaveChart() {   driver.Navigate().GoToUrl(TestHelper.SiteUrl() + "/dynamic-­ chart.html");   try {       driver.FindElement(By.TagName("svg"));       Assert.Fail("The SVG should not existed yet");   } catch (OpenQA.Selenium.NoSuchElementException e) {       Console.WriteLine("As expected");   }   driver.FindElement(By.Id("gen-btn")).Click();   System.Threading.Thread.Sleep(1000); // load JS   WebElement svg_elem = (WebElement) ­driver.FindElement(By. TagName("svg"));   Screenshot screenshot = svg_elem.GetScreenshot(); 280

Chapter 27

Case Studies

  screenshot.SaveAsFile(savedChartFilePath, ScreenshotImageFormat.Png);   Assert.IsTrue(File.Exists(savedChartFilePath));   // Add nuget System.Drawing.Common,which only supported on Windows   if (OperatingSystem.IsWindows()) {     System.Drawing.Image img = System.Drawing.Image. FromFile(savedChartFilePath);     Console.WriteLine("Width: " + img.Width + ", Height: " + img.Height);     Assert.AreEqual(400, img.Height);   } }

D  rawing on a Canvas You can use Selenium WebDriver’s Advanced User Interactions API to simulate mouse events to draw on a canvas within a web page. We will show how to draw markers on a body chart (background image) in the WhenWise app, with the following operations: •

Draw a circle and move it to the neck position of the back.



Draw a cross.



Add a piece of text to the canvas. See Figure 27-4.

281

Chapter 27

Case Studies

Figure 27-4.  The expected output in this case study is a canvas with shapes and text added The sample web site is https://whenwise.agileway.net, a sandbox (non-production) server of a comprehensive service booking app.

T est Steps 1. Find the canvas. 2. Use the canvas as the anchor point 3. Write Selenium’s Advanced User Interactions API statements.

Log In and Get to the Canvas Page This test script logs into the WhenWise sandbox server: driver.Manage().Window.Size = new System.Drawing. Size(1156, 764); driver.Navigate().GoToUrl("https://whenwise.agileway.net/ reset"); driver.FindElement(By.Id("email")).SendKeys("[email protected]"); 282

Chapter 27

Case Studies

driver.FindElement(By.Id("password")).SendKeys("test01"); driver.FindElement(By.Id("login-btn")).Click(); driver.Navigate().GoToUrl("https://whenwise.agileway.net/work_ orders/1"); These Selenium statements are straightforward. Astute readers might notice “/reset” and “/work_orders/1”. Yes, the test data is seeded in WhenWise. Data seeding is an important but often neglected practice in test automation. This is outside the topic of this book. All you need to know is that the WhenWise sandbox server provides a data reset feature to get to specific test scenarios quicker.

Set a Fixed Canvas and Choose an Anchor Point Just like any task involves drawing, maintaining a consistent canvas size is essential. You accomplish this by configuring the browser window to a specific dimension. driver.Manage().Window.Size = new System.Drawing. Size(1156, 764); With pixel drawing, you need a starting point, known as an anchor point. var elem = driver.FindElement(By.TagName("canvas")); // then using elem.Location.X and elem.Location.Y Any X,Y coordinates you use later are based on this anchor point.

Draw a Circle and Move It Like many canvas implementations, there are external buttons to put predefined shapes on the canvas. The following statement clicks a button to add a circle shape, shown in Figure 27-5.

283

Chapter 27

Case Studies

Figure 27-5.  Illustration of how the circle is added to the canvas. First, it is inserted and then it is dragged to the desired position driver.FindElement(By.Id("drawing-circle")).Click(); The main task of this step is to move the circle shape to a specific location, in this case, the position at the back of the neck. Actions builder = new Actions(driver); builder.MoveToLocation(elem.Location.X + 40, elem. Location.Y + 40); builder.ClickAndHold(); builder.MoveByOffset(450, 60); builder.Release(); builder.Perform();

Draw a Cross (Hand-Drawing Mode) Enter the drawing mode and then draw two lines. driver.FindElement(By.Id("drawing-mode")).Click(); System.Threading.Thread.Sleep(500); builder = new Actions(driver); builder.MoveToLocation(elem.Location.X + 50, elem. Location.Y + 50); builder.ClickAndHold(); builder.MoveByOffset(50, 50);

284

Chapter 27

Case Studies

builder.Release(); builder.MoveToLocation(elem.Location.X + 100, elem. Location.Y + 50); builder.ClickAndHold(); builder.MoveByOffset(-50, +50); builder.Release(); builder.Perform();

A  dd Text Invoke this from external action buttons and then enter the text in a modal dialog. driver.FindElement(By.Id("drawing-text")).Click(); System.Threading.Thread.Sleep(500); var elemTextBox = driver.FindElement(By.XPath("//div [@class='modal open']//input[@type='text' and contains(@id, 'modal-input-')]")); elemTextBox.SendKeys("Selenium WebDriver is the best!"); driver.FindElement(By.XPath("//div[@class='modal open'] /div[@class='modal-footer']/a[@data-name='confirm']")).Click();

T est Script [TestMethod] public void TestDrawCanvas() {   driver = new ChromeDriver();   driver.Manage().Window.Size = new System.Drawing. Size(1156, 764);   ­driver.Navigate().GoToUrl("https://whenwise.agileway.net/ reset");

285

Chapter 27

Case Studies

  driver.FindElement(By.Id("email")).SendKeys("physio@ biz.com");   driver.FindElement(By.Id("password")).SendKeys("test01");   driver.FindElement(By.Id("login-btn")).Click();   System.Threading.Thread.Sleep(500);   driver.Navigate().GoToUrl("https://whenwise.agileway.net/ work_orders/1");   driver.FindElement(By.Id("work-charts")).Click();   System.Threading.Thread.Sleep(1000);   var elem = driver.FindElement(By.TagName("canvas"));   // add the a circle shape   driver.FindElement(By.Id("drawing-circle")).Click();   System.Threading.Thread.Sleep(1000);   // move it to the back neck position   Actions builder = new Actions(driver);   builder.MoveToLocation(elem.Location.X + 40, elem. Location.Y + 40);   builder.ClickAndHold();   builder.MoveByOffset(450, 60);   builder.Release();   builder.Perform();   // draw X   driver.FindElement(By.Id("drawing-mode")).Click();   System.Threading.Thread.Sleep(500);   builder = new Actions(driver);   builder.MoveToLocation(elem.Location.X + 50, elem. Location.Y + 50);   builder.ClickAndHold();

286

Chapter 27

Case Studies

  builder.MoveByOffset(50, 50);   builder.Release();   builder.MoveToLocation(elem.Location.X + 100, elem. Location.Y + 50);   builder.ClickAndHold();   builder.MoveByOffset(-50, +50);   builder.Release();   builder.Perform();   // add Text   driver.FindElement(By.Id("drawing-text")).Click();   System.Threading.Thread.Sleep(500);   var elemTextBox = driver.FindElement(By.XPath("//div [@class='modal open']//input[@type='text' and contains (@id, 'modal-input-')]"));   elemTextBox.SendKeys("Selenium WebDriver is the best!");   driver.FindElement(By.XPath("//div[@class='modal open']/div [@class='modal-footer']/a[@data-name='confirm']")).Click(); }

 utomated Testing Elements on a Lazy A Load Page Some modern websites now use lazy loading (also known as progressive loading). Instead of loading everything on the page at once, the items load as you scroll. This brings challenges to test automation because the element you want to control may not be displayed yet. An example site is Substack. If you scroll down on this site, you will notice the bottom of this page reloads more articles.

287

Chapter 27

Case Studies

T est Design 1. Look for the element on the page. 2. If the element is not there, scroll down the page. 3. Repeat steps 1–2.

T est Steps 1. Look for the element on the page. If the following test steps pass, the article with the title containing “Micro-ISV 01” is displayed on the page. driver.Navigate().GoToUrl("https://agileway.substack. com/archive"); driver.FindElement(By.PartialLinkText("Micro-ISV 01")); So why is there no Click() after the FindElement? This is because we want to remain on the page. Effectively, this FindElement statement is a checking operation. driver.FindElement(By.LinkText("Fake End-to-End Test Automation Clarified")).Click(); The above test step would fail with the following error message because this past article is not shown yet: OpenQA.Selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"link text","selector":"Fake End-­to-­End Test Automation Clarified"} 288

Chapter 27

Case Studies

2. Scroll down if the element is not there. Because the element is not there yet, keep scrolling down the page until it is shown. The easiest way to do this is to send the Page Down key to the browser. driver.FindElement(By.TagName("body")).SendKeys(Keys. PageDown); Since you are uncertain about the number of times you need to scroll down, you will initially set a relatively high value, such as 100. Do set a condition to exit if the desired element is found. for (int i = 1; i