Skip to content

Commit

Permalink
Merge pull request #11 from Badgerati/develop
Browse files Browse the repository at this point in the history
v1.0.0
  • Loading branch information
Badgerati committed Oct 14, 2019
2 parents c565782 + dc6f7af commit 4b4824b
Show file tree
Hide file tree
Showing 33 changed files with 28,918 additions and 443 deletions.
58 changes: 58 additions & 0 deletions .build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Task 'Build' Selenium, { }

Task 'Selenium' {
if (Test-Path ./src/lib) {
Remove-Item -Path ./src/lib -Force -Recurse -ErrorAction Stop | Out-Null
}

if (Test-Path ./temp) {
Remove-Item -Path ./temp -Force -Recurse -ErrorAction Stop | Out-Null
}

$packages = @{
'Selenium.WebDriver' = '3.141.0'
'Selenium.Support' = '3.141.0'
'Selenium.WebDriver.ChromeDriver' = '77.0.3865.4000'
'Selenium.WebDriver.IEDriver' = '3.150.0'
'Selenium.WebDriver.GeckoDriver' = '0.26.0'
}

$packages.Keys | ForEach-Object {
nuget install $_ -source nuget.org -version $packages[$_] -outputdirectory ./temp | Out-Null
}

# web drivers
New-Item -Path ./src/lib/WebDriver -ItemType Directory -Force | Out-Null

Copy-Item -Path "./temp/Selenium.WebDriver.$($packages['Selenium.WebDriver'])/lib/*" -Destination ./src/lib/WebDriver -Recurse -Force | Out-Null
Copy-Item -Path "./temp/Selenium.Support.$($packages['Selenium.Support'])/lib/*" -Destination ./src/lib/WebDriver -Recurse -Force | Out-Null

Remove-Item -Path ./src/lib/WebDriver/net20 -Force -Recurse -ErrorAction Ignore | Out-Null
Remove-Item -Path ./src/lib/WebDriver/net35 -Force -Recurse -ErrorAction Ignore | Out-Null
Remove-Item -Path ./src/lib/WebDriver/net40 -Force -Recurse -ErrorAction Ignore | Out-Null

# browsers
New-Item -Path ./src/lib/Browsers -ItemType Directory -Force | Out-Null
New-Item -Path ./src/lib/Browsers/win -ItemType Directory -Force | Out-Null
New-Item -Path ./src/lib/Browsers/linux -ItemType Directory -Force | Out-Null
New-Item -Path ./src/lib/Browsers/mac -ItemType Directory -Force | Out-Null

# win
"./temp/Selenium.WebDriver.IEDriver.$($packages['Selenium.WebDriver.IEDriver'])/driver/*" | Out-Default
Copy-Item -Path "./temp/Selenium.WebDriver.IEDriver.$($packages['Selenium.WebDriver.IEDriver'])/driver/*" -Destination ./src/lib/Browsers/win/ -Recurse -Force | Out-Null
Copy-Item -Path "./temp/Selenium.WebDriver.GeckoDriver.$($packages['Selenium.WebDriver.GeckoDriver'])/driver/win64/*" -Destination ./src/lib/Browsers/win/ -Recurse -Force | Out-Null
Copy-Item -Path "./temp/Selenium.WebDriver.ChromeDriver.$($packages['Selenium.WebDriver.ChromeDriver'])/driver/win32/*" -Destination ./src/lib/Browsers/win/ -Recurse -Force | Out-Null

# linux
Copy-Item -Path "./temp/Selenium.WebDriver.GeckoDriver.$($packages['Selenium.WebDriver.GeckoDriver'])/driver/linux64/*" -Destination ./src/lib/Browsers/linux/ -Recurse -Force | Out-Null
Copy-Item -Path "./temp/Selenium.WebDriver.ChromeDriver.$($packages['Selenium.WebDriver.ChromeDriver'])/driver/linux64/*" -Destination ./src/lib/Browsers/linux/ -Recurse -Force | Out-Null

# mac
Copy-Item -Path "./temp/Selenium.WebDriver.GeckoDriver.$($packages['Selenium.WebDriver.GeckoDriver'])/driver/mac64/*" -Destination ./src/lib/Browsers/mac/ -Recurse -Force | Out-Null
Copy-Item -Path "./temp/Selenium.WebDriver.ChromeDriver.$($packages['Selenium.WebDriver.ChromeDriver'])/driver/mac64/*" -Destination ./src/lib/Browsers/mac/ -Recurse -Force | Out-Null

# clean up temp
if (Test-Path ./temp) {
Remove-Item -Path ./temp -Force -Recurse | Out-Null
}
}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Tests/
examples/*.jpg
examples/*.jpg
temp/
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM mcr.microsoft.com/powershell:6.2.3-ubuntu-16.04
LABEL maintainer="Matthew Kelly (Badgerati)"

# update
RUN apt-get update -y
RUN apt-get install -y unzip curl wget

# install chrome
RUN apt-get install -y libappindicator1 fonts-liberation
RUN curl https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -o /chrome.deb
RUN dpkg -i /chrome.deb; apt-get -fy install
RUN rm /chrome.deb
RUN google-chrome-stable --no-sandbox --headless --disable-gpu --dump-dom https://www.chromestatus.com/

# install firefox
RUN apt-get install -y firefox

# copy over monocle
RUN mkdir -p /usr/local/share/powershell/Modules/Monocle
COPY ./src/ /usr/local/share/powershell/Modules/Monocle

# state that monocle should be headless
RUN export DISPLAY=:99
ENV MONOCLE_HEADLESS '1'

# set as executable on drivers
RUN chown root:root /usr/local/share/powershell/Modules/Monocle/lib/Browsers/linux/chromedriver
RUN chmod +x /usr/local/share/powershell/Modules/Monocle/lib/Browsers/linux/chromedriver

RUN chown root:root /usr/local/share/powershell/Modules/Monocle/lib/Browsers/linux/geckodriver
RUN chmod +x /usr/local/share/powershell/Modules/Monocle/lib/Browsers/linux/geckodriver
167 changes: 77 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,150 +3,137 @@
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Badgerati/Monocle/master/LICENSE.txt)
[![PowerShell](https://img.shields.io/powershellgallery/dt/monocle.svg?label=PowerShell&colorB=085298)](https://www.powershellgallery.com/packages/Monocle)

Monocle is a PowerShell Web Automation module, made to make automating and testing websites easier.
Monocle is a Cross-Platform PowerShell Web Automation module, made to make automating and testing websites easier. It's a PowerShell wrapper around Selenium, with the aim of abstracting Selenium away from the user.

* [Install](#install)
* [Example](#example)
* [Documentation](#documentation)
* [Functions](#functions)
* [Screenshots](#screenshots)
* [Waiting](#waiting)
* [Docker](#docker)

Monocle currently supports the following browsers:

* IE
* Google Chrome
* Firefox

## Install

```powershell
Install-Module -Name Monocle
Import-Module -Name Monocle
```

## Example

```powershell
# if you didn't install globally, then import like so:
$root = Split-Path -Path (Split-Path -Path $MyInvocation.MyCommand.Path)
Import-Module "$root\Monocle.psm1" -DisableNameChecking -ErrorAction Stop
# if you did import globally:
Import-Module Monocle
# create a browser
$browser = New-MonocleBrowser -Type Chrome
# Monocle runs commands in web flows, for easy disposal and test tracking
# Each browser needs a name
Start-MonocleFlow -Name 'Load YouTube' -ScriptBlock {
Start-MonocleFlow -Name 'Load YouTube' -Browser $browser -ScriptBlock {
# Tell the browser which URL to navigate to, will sleep while page is loading
# tell the browser which URL to navigate to, will wait for the page to load
Set-MonocleUrl -Url 'https://www.youtube.com'
# Sets the search bar element to the passed value to query
# sets the element's value, selecting the element by ID/Name
Set-MonocleElementValue -Id 'search_query' -Value 'Beerus Madness (Extended)'
# Tells the browser to click the search button
# click the search button
Invoke-MonocleElementClick -Id 'search-btn'
# Though all commands sleep when the page is busy, some buttons use javascript
# to reform the page. The following will sleep the browser until the passed URL is loaded.
# If (default) 10 seconds passes and no URL, then the flow fails
# wait for the URL to change to start with the following value
Wait-MonocleUrl -Url 'https://www.youtube.com/results?search_query=' -StartsWith
# Downloads an image from the page. This time it's using something called MPath (Monocle Path).
# It's very similar to XPath, and allows you to pin-point elements more easily
Save-MonocleImage -MPath 'div[@data-context-item-id=SI6Yyr-iI6M]/img[0]' -Path '.\beerus.jpg'
# downloads an image from the page, selcted by using an XPath to an element
Save-MonocleImage -XPath "//div[@data-context-item-id='SI6Yyr-iI6M']/img[1]" -Path '.\beerus.jpg'
# Tells the browser to click the video in the results. The video link is found via MPath
Invoke-MonocleElementClick -MPath 'a[@title=Dragon Ball Super Soundtrack - Beerus Madness (Extended) - Duration: 10:00.]'
# tells the browser to click the video in the results
Invoke-MonocleElementClick -XPath "//a[@title='Dragon Ball Super Soundtrack - Beerus Madness (Extended)']"
# Again, we expect the URL to be loaded
# wait for the URL to be loaded
Wait-MonocleUrl -Url 'https://www.youtube.com/watch?v=SI6Yyr-iI6M'
} -Visible -ScreenshotOnFail
}
# dispose the browser
Close-MonocleBrowser -Browser $browser
```

## Documentation

### Functions

The following is a list of available functions in Monocle. These can be used, after calling `Import-Module -Name Monocle`.
The following is a list of available functions in Monocle:

* Assert-MonocleBodyValue
* Assert-MonocleElementValue
* Close-MonocleBrowser
* Edit-MonocleUrl
* Get-MonocleElementValue
* Get-MonocleHtml
* Get-MonocleUrl
* Invoke-MonocleElementCheck
* Invoke-MonocleElementClick
* Invoke-MonocleRetryScript
* Invoke-MonocleScreenshot
* New-MonocleBrowser
* Restart-MonocleBrowser
* Save-MonocleImage
* Wait-MonocleElement
* Wait-MonocleUrl
* Wait-MonocleValue
* Get-MonocleElementValue
* Start-MonocleFlow
* Edit-MonocleUrl
* Set-MonocleUrl
* Invoke-MonocoleScreenshot
* Set-MonocleElementValue
* Set-MonocleUrl
* Start-MonocleFlow
* Start-MonocleSleep
* Restart-MonocleBrowser
* Get-MonocleUrl
* Submit-MonocleForm
* Test-MonocleElement
* Wait-MonocleElement
* Wait-MonocleUrl
* Wait-MonocleUrlDifferent
* Wait-MonocleValue

The following is a list of assertions available in Monocle:
### Screenshots

* Assert-MonocleBodyValue
* Assert-MonocleElementValue
There are two main ways to take a screenshot of the browser. The first it to tell Monocle to automatically take a screenshot whenever a flow fails. You can do this by using the `-ScreenshotPath` and `-ScreenshotOnFail` parameters on the `Start-MonocleFlow` function:

### MPath

MPath, or Monocle Path, is very similar to XPath and allows you to pin-point elements more easily.
You find elements initially by tag, and then optionally by zero-based index or attribute.

For example, take the following HTML:

```html
<html>
<head>
<title>Example</title>
</head>
<body>
<form method='post' action='/example'>
<input type='text' id='SomeInput' />
<input type='text' data-type='test' />
<input type='text' data-type='test' />
<input type='submit' />
</form>
</body>
</html>
```powershell
Start-MonocleFlow -Name '<name>' -Browser $browser -ScriptBlock {
# failing logic
} -ScreenshotPath './path' -ScreenshotOnFail
```

Here we have a very basic form with 3 textual inputs and a submit button.

Let's say we want to update the value of the first textual input, the one with an ID of `SomeInput`. The MPath to select this element would be:
Or, you can take a screenshot directly:

```plain
form/input[@id=SomeInput]
```powershell
Invoke-MonocoleScreenshot -Name 'screenshot.png' -Path './path'
```

In MPath, each query for an element is split by a slash (/). Each query starts with a tag name (form or input), followed optionally by square-brackets and a filter ([@id=SomeInput]).
Splitting down on the above MPath, Monocle will first find all `form` elements on the page. Then, it will find all input elements within those forms that have an `id` of `SomeInput`.

We can also simplify the above MPath to merely just:
> Not supplying `-ScreenshotPath` or `-Path` will default to the current path.
```plain
input[@id=SomeInput]
```
### Waiting

Since Monocle only interacts with single elements, then once all queries have run the top first 1 element from the whole MPath is returned.
There are inbuilt function to wait for a URL or element. However, to wait for an element during a Set/Click call you can use the `-Wait` switch:

Let's now say we only want to update the value of the third textual input. Well, this one doesn't have an identifiers, so the MPath looks as follows:

```plain
form/input[2]
```powershell
Invoke-MonocleElementClick -Id 'element-id' -Wait
```

MPath is zero-based, so `input[2]` will select the third element in the form. Note, if you have two forms on your page, either use `form[0]` or `form[1]` else `input[2]` will literally return the third input in total.
If we just left the above as `form/input` then the input with ID of SomeInput will have been returned.
### Docker

A more complex way of selecting the third input will be as follows:
Monocle has an offical Docker image, which comes preloaded with:

```plain
form/input[@data-type=test][1]
```

Now, we will select the two inputs that have their `data-type` set to `test`, and then we will select the second of these inputs.
* Monocle (obviously!)
* Firefox
* Google Chrome

## FAQ
You can use this image to run your Monocle flows - and they will also automatically run headless.

* I keep receiving the error:
An example `Dockerfile` could be:

```plain
Creating an instance of the COM component with CLSID {0002DF01-0000-0000-C000-000000000046} from the IClassFactory
failed due to the following error: 800704a6 A system shutdown has already been scheduled. (Exception from HRESULT: 0x800704A6).
```

Solution: Open IE, open setting the Compatability Viewing. Uncheck the two check boxes.
```dockerfile
FROM badgerati/monocle:latest
COPY . /usr/src/scripts
CMD [ "pwsh", "-c", "cd /usr/src/scripts; ./flow.ps1" ]
```
3 changes: 3 additions & 0 deletions examples/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM badgerati/monocle:latest
COPY . /usr/src/scripts
CMD [ "pwsh", "-c", "cd /usr/src/scripts; ./youtube.ps1" ]
28 changes: 17 additions & 11 deletions examples/youtube.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
$path = "$($path)/src/Monocle.psm1"
Import-Module $path -Force -ErrorAction Stop
#$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
#$path = "$($path)/src/Monocle.psm1"
#Import-Module $path -Force -ErrorAction Stop
Import-Module -Name Monocle -Force -ErrorAction Stop

# Create a browser object
$browser = New-MonocleBrowser -Type Chrome

# Monocle runs commands in web flows, for easy disposal and test tracking
# Each flow needs a name
Start-MonocleFlow -Name 'Load YouTube' -ScriptBlock {
Start-MonocleFlow -Name 'Load YouTube' -Browser $browser -ScriptBlock {

# Tell the browser which URL to navigate to, will sleep while page is loading
Set-MonocleUrl -Url 'https://www.youtube.com'
Expand All @@ -13,21 +17,23 @@ Start-MonocleFlow -Name 'Load YouTube' -ScriptBlock {
Set-MonocleElementValue -Id 'search_query' -Value 'Beerus Madness (Extended)'

# Tells the browser to click the search button
Invoke-MonocleElementClick -Id 'search-btn'
Invoke-MonocleElementClick -Id 'search-icon-legacy'

# Though all commands sleep when the page is busy, some buttons use javascript
# to reform the page. The following will sleep the browser until the passed URL is loaded.
# If (default) 10 seconds passes and no URL, then the flow fails
Wait-MonocleUrl -Url 'https://www.youtube.com/results?search_query=' -StartsWith

# Downloads an image from the page. This time it's using something called MPath (Monocle Path).
# It's very similar to XPath, and allows you to pin-point elements more easily
Save-MonocleImage -MPath 'div[@data-context-item-id=SI6Yyr-iI6M]/img[0]' -Path '.\beerus.jpg'
# Downloads an image from the page. This time it's using XPath
#Save-MonocleImage -XPath "//div[@data-context-item-id='SI6Yyr-iI6M']/img[1]" -Path '.\beerus.jpg'

# Tells the browser to click the video in the results. The video link is found via MPath
Invoke-MonocleElementClick -MPath 'a[@title=Dragon Ball Super Soundtrack - Beerus Madness (Extended) - Duration: 10:00.]'
# Tells the browser to click the video in the results. The video link is found via XPath
Invoke-MonocleElementClick -XPath "//a[@title='Dragon Ball Super Soundtrack - Beerus Madness (Extended)']" -Wait

# Again, we expect the URL to be loaded
Wait-MonocleUrl -Url 'https://www.youtube.com/watch?v=SI6Yyr-iI6M'

} -Visible -ScreenshotOnFail
} -CloseBrowser -ScreenshotOnFail

# or close the browser manually:
#Close-MonocleBrowser -Browser $browser
Loading

0 comments on commit 4b4824b

Please sign in to comment.