-
Notifications
You must be signed in to change notification settings - Fork 369
06 WebView Support
Calabash supports queries and gestures on UIWebViews and WKWebViews.
There are four APIs for interacting with and inspecting web views.
We have a nice sample project that highlights each of these APIs with cucumbers.
https://github.com/calabash/ios-webview-test-app
WKWebView was added in 0.14.0 (April 2015).
The query and gesture APIs are nearly identical except for two cases.
# Matches UIWebView
> query('webView')
> query('UIWebView')
# Matches WKWebView
> query('WKWebView')
WKWebView does not respond to the stringByEvaluatingJavaScriptString:
selector. Instead it provides JavaScript evaluation with the async evaluateJavaScript:completionHandler:
selector. It was tempting to implement stringByEvaluatingJavaScriptString:
on WKWebView, but because Objective-C doesn't have namespaces it is too dangerous. It is very likely that client code or some other library will have done so already. Our implementation would either clobber or be clobbered. In practical terms this means that we needed to namespace our selector.
This is the new public API for evaluating JavaScript. It is backward compatible for UIWebView.
### Incorrect
> query("WKWebView", {stringByEvaluatingJavaScriptString:"<javascript>"})
### Correct
> query("UIWebView", {stringByEvaluatingJavaScriptString:"<javascript>"})
> query("UIWebView", {calabashStringByEvaluatingJavaScript:"<javascript>"})
> query("WKWebView", {calabashStringByEvaluatingJavaScript:"<javascript>"})
Queries will only return DOM nodes whose centers are visible on the screen and within the web view's viewport.
Consider this example of an xpath query on page whose body tag spans many screens. When the page loads, the center of the body element is a couple of swipes down. A query for the 'body' will return no results. The following step scrolls down until the center of the 'body' is visible.
And(/^I can query for the body with xpath$/) do
page = page(WebViewApp::WKWebView).await
qstr = "WKWebView xpath:'//body'"
visible = lambda {
query(qstr).count == 1
}
counter = 0
loop do
break if visible.call || counter == 4
scroll('WKWebView', :down)
sleep(0.4) # for the animation
counter = counter + 1
end
res = query(qstr)
expect(res.count).to be == 1
end
We recommend touching an input field to show the keyboard and using keyboard_enter_text
. This is what the user does and it represents an authentic test of your app.
Given(/^I have entered my email address$/) do
page(WebViewApp::WKWebView).await
qstr = "WKWebView css:'input#firstname'"
wait_for { !query(qstr).empty? }
touch(qstr)
wait_for_keyboard
keyboard_enter_text("[email protected]")
end
It is also possible to fill in a text field or text area directly using set_text
.
Be aware, however, that this may or may not trigger event listeners which in turn might affect the way your app behaves. For example, if you have a button that appears only after a valid email is entered, calling set_text
might not cause the button to appear because no event listeners are triggered.
> set_text("UIWebView css:'input.login'", "[email protected]")
Query for an element with id, class or tag name.
> query("webView css:'#header'")
> query("webView css:'.js-current-repository'")
> query("webView css:'a'")
> touch("webView css:'a#internal'")
> query("webView css:'ul#faq'")
> touch("webView css:'button#login'")
https://www.w3schools.com/xml/xpath_intro.asp
XPath is incredibly powerful, but difficult to understand until you get the hang of it.
> query("webView xpath:'//body'")
> touch("webView xpath:'//a[contains(@id,\"internal\")]'")
> touch("webView xpath:'//span/a[contains(@id,\"faq\")]'")
Unlike the css, path, and marked APIs - JavaScript can be evaluated on elements that are not visible.
> js = "document.getElementsByTagName('h1').toString()"
> query("UIWebView", {calabashStringByEvaluatingJavaScript: js})
=> ["[object NodeList]"]
> js = "document.getElementById('watermelon').innerHTML"
> query("WKWebView", {calabashStringByEvaluatingJavaScript: js})
=> ["Wassermelone"]
> js = "document.getElementById('firstname').value"
> query("UIWebView", {calabashStringByEvaluatingJavaScript: js})
=> ["[email protected]"]
> js = "document.getElementById('show_secret').click()"
> query("UIWebView", {calabashStringByEvaluatingJavaScript: js})
> js = "document.getElementById('secret_message').getAttribute('style')"
> query("WKWebView", {calabashStringByEvaluatingJavaScript: js})
=> ['display: block;']
> js = "eval(\"javascript:window.parent.navigateToSequence('border-collie-mov');\")")
> query("WKWebView", {calabashStringByEvaluatingJavaScript: js})
> iframe_id = 'frame_movies'
> element_id = 'heart-transplant'
> js = document.getElementById('#{iframe_id}').contentDocument.getElementById('#{element_id}').innerText
> query("UIWebView", {calabashStringByEvaluatingJavaScript: js})
=> ["Latest Heart Transplant Techniques"]
Starting in 0.19.0, these API have been removed.
This is a free text matcher.
// html <h1>H1 Header!<h1>
> query("UIWebView marked:'H1'").count => 1
> query("UIWebView marked:'H1 H'").count => 1
> query("UIWebView marked:'H1 Head'").count => 1
> query("UIWebView marked:'H1 Header!'").count => 1
You cannot match the web view itself using an accessibilityIdentifier
or accessibilityLabel
.
Consider this example where we've set an id and label on our Objective-C web view. Notice that the queries don't match even though the view is clearly visible to Calabash. This is because the behavior of marked:
is overridden for web views.
# Objective-C
_webView.accessibilityIdentifier = @"landing page";
_webView.accessibilityLabel = NSLocalizedString(@"landing page",
@"The app's first page.");
# Console - app is running with German language
> query("UIWebView marked:'landing page') => []
> query("UIWebView").first['id'] => "landing page"
> query("UIWebView marked:'Zielseite'") => []
> query("UIWebView").first['label'] => "Zielseite"
The work around is to use a predicate to match on the id or label. This also illustrates why you should favor using accessibilityIdentifiers
over accessibilityLabels
for Calabash queries.
> query("UIWebView {accessibilityIdentifier LIKE 'landing page'}").count => 1
# Will fail when the app language is not German.
> query("UIWebView {accessibilityLabel LIKE 'Zielseite'}").count => 1