Coder Social home page Coder Social logo

mkoehnke / wkzombie Goto Github PK

View Code? Open in Web Editor NEW
1.2K 30.0 100.0 2.58 MB

WKZombie is a Swift framework for iOS/OSX to navigate within websites and collect data without the need of User Interface or API, also known as Headless browser. It can be used to run automated tests / snapshots and manipulate websites using Javascript.

License: MIT License

Swift 96.39% Objective-C 1.23% Ruby 1.10% HTML 0.77% Shell 0.51%
swift headless browser ios osx testing

wkzombie's Introduction

WKZombie

Twitter: @mkoehnke Version Carthage compatible SPM compatible License Platform Build Status

WKZombie is an iOS/OSX web-browser without a graphical user interface. It was developed as an experiment in order to familiarize myself with using functional concepts written in Swift 4.

It incorporates WebKit (WKWebView) for rendering and hpple (libxml2) for parsing the HTML content. In addition, it can take snapshots and has rudimentary support for parsing/decoding JSON elements. Chaining asynchronous actions makes the code compact and easy to use.

Use Cases

There are many use cases for a Headless Browser. Some of them are:

  • Collect data without an API
  • Scraping websites
  • Automating interaction of websites
  • Manipulation of websites
  • Running automated tests / snapshots
  • etc.

Example

The following example is supposed to demonstrate the WKZombie functionality. Let's assume that we want to show all iOS Provisioning Profiles in the Apple Developer Portal.

Manual Web-Browser Navigation

When using a common web-browser (e.g. Mobile Safari) on iOS, you would typically type in your credentials, sign in and navigate (via links) to the Provisioning Profiles section:

Automation with WKZombie

The same navigation process can be reproduced automatically within an iOS/OSX app linking WKZombie Actions. In addition, it is now possible to manipulate or display this data in a native way with UITextfield, UIButton and a UITableView.

Take a look at the iOS/OSX demos in the Example directory to see how to use it.

Getting Started

iOS / OSX

The best way to get started is to look at the sample project. Just run the following commands in your shell and you're good to go:

$ cd Example
$ pod install
$ open Example.xcworkspace

Note: You will need CocoaPods 1.0 beta4 or higher.

Command-Line

For a Command-Line demo, run the following commands inside the WKZombie root folder:

$ swift build -Xcc -I/usr/include/libxml2 -Xlinker -lxml2

$ .build/debug/Example <appleid> <password>

Usage

A WKZombie instance equates to a web session. Top-level convenience methods like WKZombie.open() use a shared instance, which is configured with the default settings.

As such, the following three statements are equivalent:

let action : Action<HTMLPage> = open(url)
let action : Action<HTMLPage> = WKZombie.open(url)
let browser = WKZombie.sharedInstance
let action : Action<HTMLPage> = browser.open(url)

Applications can also create their own WKZombie instance:

self.browser = WKZombie(name: "Demo")

Be sure to keep browser in a stored property for the time of being used.

a. Chaining Actions

Web page navigation is based on Actions, that can be executed implicitly when chaining actions using the >>> or >>* (for snapshots) operators. All chained actions pass their result to the next action. The === operator then starts the execution of the action chain.

The following snippet demonstrates how you would use WKZombie to collect all Provisioning Profiles from the Developer Portal and take snapshots of every page:

    open(url)
>>* get(by: .id("accountname"))
>>> setAttribute("value", value: user)
>>* get(by: .id("accountpassword"))
>>> setAttribute("value", value: password)
>>* get(by: .name("form2"))
>>> submit
>>* get(by: .contains("href", "/account/"))
>>> click(then: .wait(2.5))
>>* getAll(by: .contains("class", "row-"))
=== myOutput

In order to output or process the collected data, one can either use a closure or implement a custom function taking the result as parameter:

func myOutput(result: [HTMLTableColumn]?) {
  // handle result
}

or

func myOutput(result: Result<[HTMLTableColumn]>) {
  switch result {
  case .success(let value): // handle success
  case .error(let error): // handle error
  }
}

b. Manual Actions

Actions can also be started manually by calling the start() method:

let action : Action<HTMLPage> = browser.open(url)

action.start { result in
    switch result {
    case .success(let page): // process page
    case .error(let error):  // handle error
    }
}

This is certainly the less complicated way, but you have to write a lot more code, which might become confusing when you want to execute Actions successively.

Basic Action Functions

There are currently a few Actions implemented, helping you visit and navigate within a website:

Open a Website

The returned WKZombie Action will load and return a HTML or JSON page for the specified URL.

func open<T : Page>(url: URL) -> Action<T>

Optionally, a PostAction can be passed. This is a special wait/validation action, that is performed after the page has finished loading. See PostAction for more information.

func open<T : Page>(then: PostAction) -> (url: URL) -> Action<T>

Get the current Website

The returned WKZombie Action will retrieve the current page.

func inspect<T: Page>() -> Action<T>

Submit a Form

The returned WKZombie Action will submit the specified HTML form.

func submit<T : Page>(form: HTMLForm) -> Action<T>

Optionally, a PostAction can be passed. See PostAction for more information.

func submit<T : Page>(then: PostAction) -> (form: HTMLForm) -> Action<T>

Click a Link / Press a Button

The returned WKZombie Actions will simulate the interaction with a HTML link/button.

func click<T: Page>(link : HTMLLink) -> Action<T>
func press<T: Page>(button : HTMLButton) -> Action<T>

Optionally, a PostAction can be passed. See [PostAction](#Special- Parameters) for more information.

func click<T: Page>(then: PostAction) -> (link : HTMLLink) -> Action<T>
func press<T: Page>(then: PostAction) -> (button : HTMLButton) -> Action<T>

Note: HTMLButton only works if the "onClick" HTML-Attribute is present. If you want to submit a HTML form, you should use Submit instead.

Find HTML Elements

The returned WKZombie Action will search the specified HTML page and return the first element matching the generic HTML element type and passed SearchType.

func get<T: HTMLElement>(by: SearchType<T>) -> (page: HTMLPage) -> Action<T>

The returned WKZombie Action will search and return all elements matching.

func getAll<T: HTMLElement>(by: SearchType<T>) -> (page: HTMLPage) -> Action<[T]>

Set an Attribute

The returned WKZombie Action will set or update an existing attribute/value pair on the specified HTMLElement.

func setAttribute<T: HTMLElement>(key: String, value: String?) -> (element: T) -> Action<HTMLPage>

Execute JavaScript

The returned WKZombie Actions will execute a JavaScript string.

func execute(script: JavaScript) -> (page: HTMLPage) -> Action<JavaScriptResult>
func execute(script: JavaScript) -> Action<JavaScriptResult>

For example, the following example shows how retrieve the title of the currently loaded website using the execute() method:

    browser.inspect
>>> browser.execute("document.title")
=== myOutput

func myOutput(result: JavaScriptResult?) {
  // handle result
}

The following code shows another way to execute JavaScript, that is e.g. value of an attribute:

    browser.open(url)
>>> browser.get(by: .id("div"))
>>> browser.map { $0.objectForKey("onClick")! }
>>> browser.execute
>>> browser.inspect
=== myOutput

func myOutput(result: HTMLPage?) {
  // handle result
}

Fetching

Some HTMLElements, that implement the HTMLFetchable protocol (e.g. HTMLLink or HTMLImage), contain attributes like "src" or "href", that link to remote objects or data. The following method returns a WKZombie Action that can conveniently download this data:

func fetch<T: HTMLFetchable>(fetchable: T) -> Action<T>

Once the fetch method has been executed, the data can be retrieved and converted. The following example shows how to convert data, fetched from a link, into an UIImage:

let image : UIImage? = link.fetchedContent()

Fetched data can be converted into types, that implement the HTMLFetchableContent protocol. The following types are currently supported:

  • UIImage / NSImage
  • Data

Note: See the OSX example for more info on how to use this.

Transform

The returned WKZombie Action will transform a WKZombie object into another object using the specified function f.

func map<T, A>(f: T -> A) -> (element: T) -> Action<A>

This function transforms an object into another object using the specified function f.

func map<T, A>(f: T -> A) -> (object: T) -> A

Taking Snapshots

Taking snapshots is available for iOS. First, a snapshotHandler must be registered, that will be called each time a snapshot has been taken:

WKZombie.sharedInstance.snapshotHandler = { snapshot in
    let image = snapshot.image
}

Secondly, adding the >>* operator will trigger the snapshot event:

    open(url)
>>* get(by: .id("element"))
=== myOutput

Note: This operator only works with the WKZombie shared instance.

Alternatively, one can use the snap command:

    browser.open(url)
>>> browser.snap
>>> browser.get(by: .id("element"))
=== myOutput

Take a look at the iOS example for more information of how to take snapshots.

Special Parameters

1. PostAction

Some Actions, that incorporate a (re-)loading of webpages (e.g. open, submit, etc.), have PostActions available. A PostAction is a wait or validation action, that will be performed after the page has finished loading:

PostAction Description
wait (Seconds) The time in seconds that the action will wait (after the page has been loaded) before returning. This is useful in cases where the page loading has been completed, but some JavaScript/Image loading is still in progress.
validate (Javascript) The action will complete if the specified JavaScript expression/script returns 'true' or a timeout occurs.

2. SearchType

In order to find certain HTML elements within a page, you have to specify a SearchType. The return type of get() and getAll() is generic and determines which tag should be searched for. For instance, the following would return all links with the class book:

let books : Action<HTMLLink> = browser.getAll(by: .class("book"))(page: htmlPage)

The following 6 types are currently available and supported:

SearchType Description
id (String) Returns an element that matches the specified id.
name (String) Returns all elements matching the specified value for their name attribute.
text (String) Returns all elements with inner content, that contain the specified text.
class (String) Returns all elements that match the specified class name.
attribute (String, String) Returns all elements that match the specified attribute name/value combination.
contains (String, String) Returns all elements with an attribute containing the specified value.
XPathQuery (String) Returns all elements that match the specified XPath query.

Operators

The following Operators can be applied to Actions, which makes chained Actions easier to read:

Operator iOS OSX Description
>>> x x This Operator equates to the andThen() method. Here, the left-hand side Action will be started and the result is used as parameter for the right-hand side Action. Note: If the right-hand side Action doesn't take a parameter, the result of the left-hand side Action will be ignored and not passed.
>>* x This is a convenience operator for the snap command. It is equal to the >>> operator with the difference that a snapshot will be taken after the left Action has been finished. Note: This operator throws an assert if used with any other than the shared instance.
=== x x This Operator starts the left-hand side Action and passes the result as Optional to the function on the right-hand side.

Authentication

Once in a while you might need to handle authentication challenges e.g. Basic Authentication or Self-signed Certificates. WKZombie provides an authenticationHandler, which is invoked when the internal web view needs to respond to an authentication challenge.

Basic Authentication

The following example shows how Basic Authentication could be handled:

browser.authenticationHandler = { (challenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) in
	return (.useCredential, URLCredential(user: "user", password: "passwd", persistence: .forSession))
}

Self-signed Certificates

In case of a self-signed certificate, you could use the authentication handler like this:

browser.authenticationHandler = { (challenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) in
	return (.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}

Advanced Action Functions

Batch

The returned WKZombie Action will make a bulk execution of the specified action function f with the provided input elements. Once all actions have finished executing, the collected results will be returned.

func batch<T, U>(f: T -> Action<U>) -> (elements: [T]) -> Action<[U]>

Collect

The returned WKZombie Action will execute the specified action (with the result of the previous action execution as input parameter) until a certain condition is met. Afterwards, it will return the collected action results.

func collect<T>(f: T -> Action<T>, until: T -> Bool) -> (initial: T) -> Action<[T]>

Swap

Note: Due to a XPath limitation, WKZombie can't access elements within an iframe directly. The swap function can workaround this issue by switching web contexts.

The returned WKZombie Action will swap the current page context with the context of an embedded <iframe>.

func swap<T: Page>(iframe : HTMLFrame) -> Action<T>
func swap<T: Page>(then postAction: PostAction) -> (iframe : HTMLFrame) -> Action<T>

The following example shows how to press a button that is embedded in an iframe:

    browser.open(startURL())
>>> browser.get(by: .XPathQuery("//iframe[@name='button_frame']"))
>>> browser.swap
>>> browser.get(by: .id("button"))
>>> browser.press
=== myOutput

Test / Debug

Dump

This command is useful for debugging. It prints out the current state of the WKZombie browser represented as DOM.

func dump()

Clear Cache and Cookies

Clears the cache/cookie data (such as login data, etc).

func clearCache()

Logging

WKZombie logging can be enabled or disabled by setting the following Logger variable:

Logger.enabled = false

User Agent

The user agent of WKZombie can be changed by setting the following variable:

browser.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 9_0 like Mac OS X) AppleWebKit/601.1.32 (KHTML, like Gecko) Mobile/13A4254v"

Timeout

An operation is cancelled if the time it needs to complete exceeds the time specified by this property. The default value is 30 seconds.

browser.timeoutInSeconds = 15.0

Load Media Content

This value is 'true' by default. If set 'false', the loading progress will finish once the 'raw' HTML data has been transmitted. Media content such as videos or images won't be loaded.

browser.loadMediaContent = false

Show Network Activity

If set to true, it will show the network activity indicator in the status bar. The default is true.

browser.showNetworkActivity = true

HTML Elements

When using WKZombie, the following classes are involved when interacting with websites:

HTMLPage

This class represents a read-only DOM of a website. It allows you to search for HTML elements using the SearchType parameter.

HTMLElement

The HTMLElement class is a base class for all elements in the DOM. It allows you to inspect attributes or the inner content (e.g. text) of that element. Currently, there are 7 subclasses with additional element-specific methods and variables available:

  • HTMLForm
  • HTMLLink
  • HTMLButton
  • HTMLImage
  • HTMLTable
  • HTMLTableColumn
  • HTMLTableRow

Additional subclasses can be easily implemented and might be added in the future.

JSON Elements

As mentioned above, WKZombie as rudimentary support for JSON documents.

Methods and Protocols

For parsing and decoding JSON, the following methods and protocols are available:

Parsing

The returned WKZombie Action will parse Data and create a JSON object.

func parse<T: JSON>(data: Data) -> Action<T>

Decoding

The following methods return a WKZombie Action, that will take a JSONParsable (Array, Dictionary and JSONPage) and decode it into a Model object. This particular Model class has to implement the JSONDecodable protocol.

func decode<T : JSONDecodable>(element: JSONParsable) -> Action<T>
func decode<T : JSONDecodable>(array: JSONParsable) -> Action<[T]>

JSONDecodable

This protocol must be implemented by each class, that is supposed to support JSON decoding. The implementation will take a JSONElement (Dictionary<String : AnyObject>) and create an object instance of that class.

static func decode(json: JSONElement) -> Self?

Example

The following example shows how to use JSON parsing/decoding in conjunction with WKZombie:

    browser.open(bookURL)
>>> browser.decode
=== myOutput
func myOutput(result: Book?) {
  // handle result
}

Installation

To integrate WKZombie into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'WKZombie'

Then, run the following command:

$ pod install

To integrate WKZombie into your Xcode project using Carthage, specify it in your Cartfile:

github "mkoehnke/WKZombie"

To build WKZombie using the Swift Package Manager, add it as dependency to your Package.swift file and run the following command:

swift build -Xcc -I/usr/include/libxml2 -Xlinker -lxml2

FAQ

How can I use WKZombie and Alamofire in the same project?

When using Alamofire and WKZombie in the same project, you might encounter a collision issue with keyword Result like this:

'Result' is ambiguous for type lookup in this context

This is due to the fact, that both modules use the same name for their result enum type. The type can be disambiguated using the following syntax in that particular file:

import enum WKZombie.Result

From this point on, Result unambiguously refers to the one in the WKZombie module.

If this would still be ambiguous or sub-optimal in some files, you can create a Swift file to rename imports using typealiases:

import enum WKZombie.Result
typealias WKZombieResult<T> = Result<T>

For more information, take a look at the solution found here.

Contributing

See the CONTRIBUTING file for how to help out. You'll need to run

$ Scripts/setup-framework.sh

in the root WKZombie directory to set up a buildable framework project (WKZombie.xcworkspace).

TODOs

  • More Unit Tests
  • More examples
  • Replace hpple with more 'Swifty' implementation
  • More descriptive errors

Author

Mathias Köhnke @mkoehnke

More Resources

Attributions

License

WKZombie is available under the MIT license. See the LICENSE file for more info.

Recent Changes

The release notes can be found here.

wkzombie's People

Contributors

insidegui avatar lkaihua avatar mkoehnke avatar mrs- avatar onevcat avatar vojto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wkzombie's Issues

Submit form doesn't work

Hey,
I'm planning to use WKZombie in an upcoming project. I'm trying to login to a website, by setting the value attributes of some input elements and submit a form. I Have experience as an iOS developer, but I'm quite new to the web development (HTML/CSS/Javascript), so maybe I'm misunderstanding something. I'm using the same process as the WKZombie Demo Application.

let action = open(loginUrl)
    >>* get(by: SearchType.id("m_Content_username2"))
    >>> setAttribute("value", value: credentials.username)
    >>* get(by: SearchType.id("password2"))
    >>> setAttribute("value", value: credentials.password)
    >>* get(by: SearchType.id("aspnetForm"))
    >>> submit(then: PostAction.wait(3.0))
    >>* get(by: SearchType.id("s_m_HeaderContent_picctrlthumbimage"))
        
action.start { (result) in
    print(result)
}

This gives me the following Log:

REQUEST
https://www.lectio.dk/lectio/163/login.aspx
[....]

SCRIPT
getElementByXpath("//*[@id='m_Content_username2']").setAttribute("value", "xxx"); document.documentElement.outerHTML;
[]

SCRIPT
getElementByXpath("//*[@id='password2']").setAttribute("value", "yyy"); document.documentElement.outerHTML;
[]

SCRIPT
document.getElementById('aspnetForm').submit();
[..................................]

It find all elements and sets the right attributes. When it's has submitted the form and waited for 3 seconds, it can't find the last element. When looking at the snapshots, I see that the attributes get set right, but on the last screenshot I see that the browser is still on the login page, with the password reset. I sure it's the right credentials. It seems that the form isn't submitted right. The printet result is "Element Not Found".
Here is the HTML of the form:

<form method="post" action="./login.aspx" onsubmit="javascript:return WebForm_OnSubmit();" onkeypress="javascript:return WebForm_FireDefaultButton(event, 'm_defaultformbtn')" id="aspnetForm" class="ls-master-container2" autocomplete="off">
<!— Lots of HTML and stuff —>
</form>

I've also tried:

  • with a local instance of WKZombie, but with same result.
  • to execute the JavaScript, by running execute("WebForm_OnSubmit()"). The JavaScript result was a Success, but I could seem to go on from there. How would I load the following page?
  • finding the Login-button element and running click, but doesn't work either. It is executing for a while, and the I get the "Cancelling Rendering"-error.

This might not be an issue with WKZombie, but any clues would be much appreciated!! :)
Great work with this project 👍

Option for additional http headers

It would be great if you could set http headers like Cookies, etc.

You've already added support for the user-agent via browser.userAgent – could you also include the rest of the standard http headers?

Chaining Functions

I wanted to know if it was possible for me to chain different scrapes. Modifying the example, you can see what I mean. Also, it would be cool know how to get the current url and pass it to the next function

func getProvisioningProfiles(_ url: URL, user: String, password: String) {
               open(url)
           >>* get(by: .id("account name")
           >>> setAttribute("value", value: user)
           >>* get(by: .id("account password")
           >>> setAttribute("value", value: password)
           >>* get(by: .name("form2"))
           >>> submit(then: .wait(2.0))
           >>* get(by: .contains("href", "/account/"))
           >>> click(then: .wait(2.5))
           >>* getAll(by: .contains("class", "row-"))
           === getSomeOtherInfo
    }
    func getSomeOtherInfo(_ url: URL) {
               open(url2)
           >>* get(by: .id("account name")
           >>> setAttribute("value", value: user)
           >>* get(by: .id("account password")
           >>> setAttribute("value", value: password)
           >>* get(by: .name("form2"))
           >>> submit(then: .wait(2.0))
           >>* get(by: .contains("href", "/account/"))
           >>> click(then: .wait(2.5))
           >>* getAll(by: .contains("class", "row-"))
           === handleResult
    }


    //========================================
    // MARK: Handle Result
    //========================================

    func handleResult(_ result: Result<[HTMLTableRow]>) {
        switch result {
        case .success(let value): self.outputResult(value)
        case .error(let error): self.handleError(error)
        }
    }

    func outputResult(_ rows: [HTMLTableRow]) {
        let columns = rows.flatMap { $0.columns?.first }
        performSegue(withIdentifier: "detailSegue", sender: columns)
    }

    func handleError(_ error: ActionError) {
        print("Error loading page: \(error)")
        loginButton.isEnabled = true
        activityIndicator.stopAnimating()

        dump()
        clearCache()
    }
```))))

Build error: 'shared' is unavailable: Use view controller based solutions where appropriate instead.

Pods/WKZombie/Sources/WKZombie/Renderer.swift:91:47: error: 'shared' is unavailable: Use view controller based solutions where appropriate instead.
                if let window = UIApplication.shared.keyWindow {
                                              ^~~~~~
UIKit.UIApplication:5:20: note: 'shared' has been explicitly marked unavailable here
    open class var shared: UIApplication { get }
PODS:
  - hpple (0.2.0)
  - WKZombie (1.1.0):
    - hpple (= 0.2.0)

iOS 11.1
Xcode 9.1

How to follow redirect?

It seems like WKZombie does not follow the redirect when I press a button on my webpage. It logs

SCRIPT
window.location.href='URL...';
[..............................................

How can I follow the new redirect?

How to disable JavaScript in WKZombie

So far there is not an option to disable JavaScript:

browser.javascriptEnabled = false

Under particular circumstances I would like to access pages without enabling JavaScript, and then get the content with xPath.

Thanks for your reply in the twitter.

URL unable to parse/Bypass robots.txt

I have an https url that isn't able to parse. Using other methods, I've needed to bypass robots.txt, but it does not seem exist any setting for this in WKZombie?

Receiving Error when building WKZombie

I have received the following error when building WKZombie (Swift Version 4.0 - macOS High Sierra 10.13):

/WKZombie/Renderer.swift:99:47: error: method 'shared' was used as a property; add () to call it
if let window = NSApplication.shared.keyWindow, let view = window.contentView {

Any help would be greatly appreciated.

execute script not return underlying error

The result always wraps ActionError.networkRequestFailure when browser execute script get the error.
But the underlying WKError could be helpful and real error reason in this case.

So could we make change to return the underlying error when execute script?

Show network activity indicator

When WKZombie is loading a webpage or fetching content online, it would be good to show network activity indicator on the status bar by internally increasing and decreasing network activity count.

Or, WKZombie can send notifications to allow users handle network activity indicator themselves.

=== problem

Hi,
I tried using WKZombie for this code

browser.open(url)

browser.get(by: .Id("Username"))
browser.setAttribute("value", value: username)
browser.get(by: .Id("Password"))
browser.setAttribute("value", value: password)
browser.get(by: .Id("site-login-form"))
browser.submit(then: .Wait(2.0))
browser.get(by: .Contains("href", "#studentmyday/schedule"))
browser.click(then: .Wait(2.0))
browser.getAll(by: .Id("accordionSchedules"))
=== handleResult

and it returns the error:
Binary operator '===' cannot be applied to operands of type 'Action<[HTMLElement]>' (aka 'Action<Array>') and '(Action<[HTMLTableRow]>) -> ()'

. All the methods are the same with the sample files and I am sure I have the update version. Any ideas?
Thanks!

HTTPS request unable to parse

Cancelling Rendering - Optional("REQUEST\nhttps://192.168.0.1:12380/")
.2016-10-26 21:09:43.529 DYC[12587:442455] Unable to parse.

I can give you an external address to this if you need it, it is using a self sign cert just wondering how to get round this

Update to Swift 3?

This framework looks perfect for what I've been looking for! The only problem is that my app is made in Swift 3, and yours seems to be older than that. I installed the Cocoapod, and there were 99 errors! The reason is because it needs to be updated. So is there an update to WKZombie for Swift 3 comming anytime soon?

UI API called on a background thread - iOS 11 - Xcode 9

Hi!
FYI:
WKZombie unfortunately does not work on iOS 11. The NSOperationQueue is of type USER_INITIATED and calls to WKWebView requires to be on the main thread.

With the new Main Thread Checker in XCode 9 it prints out the following in the console:

  • Main Thread Checker: UI API called on a background thread: -[WKWebView isLoading]
  • Main Thread Checker: UI API called on a background thread: -[WKWebView configuration]
  • Main Thread Checker: UI API called on a background thread: -[WKUserContentController addScriptMessageHandler:name:]
  • Main Thread Checker: UI API called on a background thread: -[WKWebView setNavigationDelegate:]
  • Main Thread Checker: UI API called on a background thread: -[WKWebView loadRequest:]
  • Main Thread Checker: UI API called on a background thread: -[WKWebView evaluateJavaScript:completionHandler:]

Best regards,
Erik

Get all HTMLRows in HTMLTable on page

Hi,

I'm trying to convert my backend scraper to an on-device scraper using your library, but I'm finding it hard to understand the documentation without examples, how would one use WKZombie to login and get a HTML Table, and how do you debug it? I can't seem to get document.title to print anything other than nil. I'm opening an URL that will redirect me to a page, where I can login.

I have the following code for now:

func testScraper(_ url: URL, user: String, password: String) {
    open(url)
        >>> get(by: .id("username"))
        >>> setAttribute("value", value: user)
        >>> get(by: .id("password"))
        >>> setAttribute("value", value: password)
        >>> get(by: .name("f"))
        >>> submit(then: .wait(2.0))
        >>> getAll(by: .contains("class", "DataSelect"))
        === handleResult
}

But I'm just getting a "error loading page: Not found" when printing the result, but I can't debug it without knowing if it has been redirected before It tries to get the table and forth? And do you recommend using browser.* instead of the >>> operators? I'm having a very difficult time adjusting the example code to my own use-case, as I keep getting errors like "operands here have types 'Action' and '([HTMLElement]) -> ()'.

All the best,
Christian

Help with OpenThen, Batch, Fetch Functions

Hi @mkoehnke!

Thank You:
First, thank you for building this repo, I've only been able to use some of the basic features, but it's already been incredibly useful!

Goal:
I'm using WKZombie to open a website, login, wait for redirect, scrape video links, scrape each video source, and download the linked files. The video source is on the individual page of each video link.

Problems:

  • Signing into social networks like LinkedIn.
  • Performing many URL Requests at once causes the host to block access.

Request:
Could you please help teach how to login to LinkedIn, delay URL requests, and use openThen?

Additional Notes:

  • Read WKZombie manual, reviewed iOS and MacOS demo, reviewed swift files in WKZombie pod, and searched Stack Overflow, Google, YouTube, and GitHub for examples.
  • Using WKZombie with SwiftSoup.

Receiving "Cancelling Rendering" Message when trying to log in to certain website

I am trying to log in to a certain website with the following code:

open(url) >>> get(by: .name("fdsa")) >>> setAttribute("value", value: user) >>> get(by: .name("fdsa")) >>> setAttribute("value", value: pw) >>> get(by: .name("loginform")) >>> submit === output

After it submits the login form it fails to render the new page form some unknown reason...

Button NIL even though Get returns the Button

 let url = URL(string: "http://192.168.2.5")!
        open(then: .wait(10.0))(url)
    >>> get(by: .id("UserName"))
    >>> setAttribute("value", value: "xxx")
    >>> get(by: .id("Password"))
    >>> setAttribute("value", value: "xyz")
    >>> get(by: .class("submitBtn"))
    >>> press(then: .wait(10.0))
    === { (apage: HTMLPage?) in
            print("\(apage)")
        }

this returns "nil" and I cannot figure out why. When I look at the logs, it says the node was nil.

SCRIPT
getElementByXpath("//*[@id='UserName']").setAttribute("value", "xxx"); document.documentElement.outerHTML;
[.]

SCRIPT
getElementByXpath("//*[@id='Password']").setAttribute("value", "xyz"); document.documentElement.outerHTML;
[]

nil

The following code fetches the submit button just fine, which is why I'm surprised the snippet above doesn't work. What am I missing?

 let url = URL(string: "http://192.168.2.5")!
        open(then: .wait(10.0))(url)
    >>> get(by: .id("UserName"))
    >>> setAttribute("value", value: "xxx")
    >>> get(by: .id("Password"))
    >>> setAttribute("value", value: "xyz")
    >>> get(by: .class("submitBtn"))
    === { (apage: HTMLElement?) in
            print("\(apage)")
        }

Question: html -> pdf?

Can WKZombie be used to generate a PDF file from the rendering of a webpage via either https and/or file url? (preferrably not as a image raster which is then placed into PDF)

Creating Action Functions

I want to know how do I create actions so I can continue using headless browser even after calling the function. In a sense, I want to mimic the WKZombie methods but on a parsing function.

Limited to iOS 9.1?

Is there a specific reason this pod can't be used below iOS 9? I have an iOS 8 and up project that could benefit from WKZombie :)

click() assumes links?

I have an use case where I need to click a <button> on the page. I tried using >>> click(), but It looks like that action assumes the target to be an <a> tag. How can I use WKZombie to click a button on the webpage?

Cannot check reference equality of functions; operands here have types 'Action<HTMLElement>' and '(Result<[HTMLElement]>) -> ()'

get the error above for the following code

` func enterContests()
{
open(URL(string: "https://prizegrab.com")!)
>>> get(by: .XPathQuery("//a[@Class='login-link']"))
>>> click(then: .wait(1.5))
>>> get(by: .id("login-with-email"))
>>> click(then: .wait(1.5))
>>> get(by: .id("login-email"))
>>> setAttribute("value", value: username.stringValue)
>>> get(by: .id("login-password"))
>>> setAttribute("value", value: pass.stringValue)
>>> get(by: .XPathQuery("//div[contains(@Class,'form-login')]/form"))
>>> submit(then: .wait(3))
>>> get(by: .contains("href", "/logout"))
=== handleResult
}

func handleResult(_ result: Result<[HTMLElement]>) {
    switch result {
    case .success(let value): errors = "no errors"
    case .error(let error): errors = error.debugDescription
    }
}

`
the line ===handleResult is the offending line

I'm not sure what I did wrong

Framework

Having built the WKZombie project and taken the framework from the Products folder and imported this into my own project. It tells me that WKZombie is unavailable and cannot find the swift class. Have I built your project incorrectly or am I doing something wrong if i want to use this on an IOS project?

Argument labels '(url:)' do not match any available overloads

Trying to use this code:

import WKZombie
let url = "https://www01.swrdc.wa-k12.net/scripts/cgiip.exe/WService=wvancous71/mobilelogin.w"
let browser = WKZombie(name: "Hello")
browser.open(url: NSURL(string: url)!)

I get the error "Argument labels '(url:)' do not match any available overloads", from that fourth line. Any ideas how I can fix this? (This is a sample of my code - I can provide the rest if need-be)

AppStore Approval

Have you successfully published apps to the AppStore that include this framework?

Make version 1.1.0 released version

When I pull in podfile for my project with:

pod 'WKZombie'

I get version 1.0.8 which has compile errors with Swift 4 (as you might imagine!)

Can you make 1.1.0 the official release and push it to the pod file repo?

Form submission

Hello, is it possible to submit a form without the form's name or just with the id?

I tried doing:
browser.get(by: .Id("theId") ) or browser.get(by: .Name(""))

browser.submit ()

and the browser.dump just returns ... after it returns the html of the page.

Sorry for having too many questions and thanks in advance!

Is their a way to render in a specific view?

Great framework!!

I've searched for a way to render the automated interaction on another view other the main one but without luck. Is there a way to render in a specific view?

PostAction.wait after execute JavaScript?

Is there any way to make the browser instance wait after .execute JavaScript?

I am executing my own javascript. From the network requests in my MITM proxy I can see the expected requests being executed from the click of the login button. However I believe the result is handled too early. The body of the page is still the one of the browser before executing the document.getElementById('ContentPlaceHolder1_btnlogin').click() .

browser.open(loginUrl) >>> browser.execute("document.getElementById('ContentPlaceHolder1_UserName').value='xxxxxx';document.getElementById('ContentPlaceHolder1_Password').value='yyyyyyy!';document.getElementById('ContentPlaceHolder1_btnlogin').click();") >>> browser.inspect() >>> browser.get(by: .XPathQuery("//body")) === handleResult

question - trying to fill in an angular form

open(url)
>>> get(by: .id("loginPassword"))
>>> setAttribute("value", value: "password")
>>> execute("angular.element($('#loginPassword')).triggerHandler('input')")
>>> inspect
>>> get(by: .id("formLogin"))
>>> submit

but the js is not triggering the handler as password is rejected. this works in console in chrome

pointers please

How to run the Example

Hi all, I tried to run the example but get the following issue:

[!] CocoaPods could not find compatible versions for pod "WKZombie":
In Podfile:
WKZombie (from ../)

Specs satisfying the WKZombie (from ../) dependency were found, but they required a higher minimum deployment target.

Any suggestions?

Using === doesn't compile

Hi!
I'm trying to use WKZombie with following code:

func getMainPage(username: String, password: String){
        let url = NSURL(string:"https://abc.com/cde")!
        browser.open(url)
        >>> browser.get(by: .Id("Id"))
        >>> browser.setAttribute("value", value: username)
        >>> browser.get(by: .Id("Pass"))
        >>> browser.setAttribute("value", value: password) 
       browser.inspect()
        >>> browser.execute("dothejob();")
        browser.inspect()
        >>> browser.getAll(by: .Id("Some_Element"))
        === handleResult
}

However XCode gives me

Binary operator '===' cannot be applied to operands of type 'Action<[HTMLElement]>' (aka 'Action<Array<HTMLElement>>') and '(Result<HTMLTableRow>) -> ()'

The handleResult method is identical to the method in the example project.
I haven't tried the whole example project, but when I replace my file with the contents of LoginViewController.swift from the example, I get the same warning.

What am I missing?

Get HTML of current page.

I'm playing around with WKZombie and I must say I enjoy it a lot! Great work.
A few questions have come up and I'm wondering if I could get some input.

  • What is the best/ most efficient way to get all the HTML of the current page?

I have tried inspect >>> execute("document.documentElement.outerHTML.toString()") where I evaluate the JavaScriptResult, but it's not ideal.

  • When I use execute does it disregard the wait time for previous Action? I.e:

                  `
                  >>> click(then: .wait(10.0))
                  >>> execute("document.documentElement.outerHTML.toString()")
                  `
    

In the above example the JavaScriptResult is nil. My guess is because the previous >>> get was a href and is not a document? How can I reference the page after a click?

  • If I'm logging in to a website and get an element by ID it works fine. If I later want to get another element I am struggeling to find a good way to run a similar operation. I.e: If the webView is already logged in the login sequence will fail as the login form does not exist. What is the best way to identify that the webView is already logged in?

  • How does WKZombie handle input values containing special characters? I'm thinking especially of the backslash (\) character.

Thanks!

Swift Package Manager - unsupported layout

Hi I am receiving the following whenever I try to download using SPM.

error: the package has an unsupported layout, unexpected source file(s) found: /Users/Reid/Developer/airbnb_wishlister/Packages/WKZombie-1.0.6/Tests/Tests.swift fix: move the file(s) inside a module

Screenshot

Hi,
Is there anyway to have screenshot of rendered content?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.