Currently, I have to create automation ui test with XCUI and I have some action to open external browser by default is Safari.
I need to create some uitest's behavior like this.
Go to external browser when click my button.
Check is open correct url in browser (or just open browser) ?
Go back to our app with resume state.
Is it impossible to do that ?.
Absolutely. Safari is an XCUIApplication just like any other with a bundle identifier of com.apple.mobilesafari.
To check the URL you'll tap() the URL field (it's a button when a page loads) and read the value of the URL field (at this point it's a textField). Once you're done asserting that, activate() your app and you'll be back in it (note: my tests finish after asserting so I don't have to do this, but it's the published method - you could always enter debug and find how to tap the button to return to your application in the top-left of the screen if this doesn't work).
I'm happy to provide exact code if you show me you've tried this and can't get it working, but it's pretty straightforward XCUI automation.
Providing the exact code, that worked for me
enum SafariError: Error {
case appLoadTimeout
}
let safari = XCUIApplication(bundleIdentifier: "com.apple.mobilesafari")
app.buttons["MyButtom"].tap() // MyButton launches Safari
guard safari.wait(for: .runningForeground, timeout: 5) else {
throw SafariError.appLoadTimeout
}
safari.otherElements["Address"].tap()
XCTAssertEqual(safari.textFields["Address"].value as! String, "https://check-url.com")
app.activate() //Back to my app
The previous answers work fine for an external browser. If you app happens to launch inline browsers, they won't work as there is no URL bar. You could detect the inline browser and tap the Safari button to launch it externally before checking the URL, but then you'd have to reactivate your app and close the inline browser. Many more steps.
THERE IS AN EASIER WAY THAT WILL WORK WITH BOTH BROWSER TYPES!
What do these browsers share? A share button! How can we get the URL from there? Copy will place the URL in the pasteboard, which we have access to.
app.buttons["ShareButton"].tap()
_ = copyButton.waitForExistence(timeout: Waits.short.rawValue) // a tiny wait is necessary here for the share page to open fully
app.buttons["Copy"].tap()
let URL = UIPasteboard.general.string!
There are two catches here:
When inline, app is your app. When external, it's com.apple.mobilesafari. You'll have to have logic to handle that.
You want to wait for the page to finish loading. By not doing so, you will run into flakiness. When the page is loading, there is a ReloadButton. When that goes away, you should expect to see a StopButton. If the reload button doesn't disappear, the page isn't loading completely so you may need to hit the stop button, but do so in a safe way as I have seen maybe 1% of the time the page load completes in the milliseconds between giving up on it and performing that tap - I do a quick isHittable check before tapping.
Related
I have a very specific issue with the wkwebview. When a user opts to use the Settings / Screen Time / Content Restrictions / web content / Allowed Websites Only then the user will be presented with a Native ui component which says Restricted Site and gives the user an option to allow the website via a Allow website button. The problem is that the website exception is indeed added to allowed list in setting but the following action of the button is blocked.
[Process] 0x10781e618 - [pageProxyID=8, webPageID=9, PID=6619] WebPageProxy::Ignoring request to load this main resource because it was handled by content filter
That means the user is stuck on the screen, which of cause is not ideal. To solve this we added an alert with the option to return to the login page (better messaging is required).
I have tried all WKNavigationDelegate and WKUIDelegate hooks which are not trigger upon clicking on the Allow Website button and I could not find any documentation around that feature. I guess the only way is to educate the user by showing a dialog with instruction to add a bunch of urls to the allowed sites section of screen time and then restart the app or reload the webview via the dialog. The whole process seems very clunky.
If anyone has some more information about this it would highly appreciated.
UPDATE:
Turns out that screen time has 2 ways of dealing with restricted content. With or without a passcode. If a passcode is set upon clicking the allow website button you will open a enter passcode dialog, which will reload the webview when entered correctly. If you don't have a passcode set the webview will not reload. In my answer below I was using a javascript to reload the webview after the html button was clicked (the shown view is not native but a local html file.) The problem with that approach is that it will crash the the app if a passcode is set.
I was hoping to find a way to know if the passcode screen has been presented but I was not successful yet. If I had that information then I could stop the webview from reloading and therefor stop the crash.
I have been learning more about this, and as it turns out the screen which shows the Restricted Site message isn't a native component but rather a system html file which gets loaded into the webview.
file:///System/Library/PrivateFrameworks/WebCore.framework/ContentFilterBlockedPage.html
The Allow website link contains a href of x-apple-content-filter://unblock which when clicked will add the blocked site to the allowed websites list (it seems to be handled outside of the WKNavigationDelegate scope). In order to refresh the webview, you need to inject a small javascript which reloads the page after you clicked the link.
(function(){
const element = document.querySelector('#unblock a');
console.log("####unblock####");
console.log("ELEMENTS", element);
if (element) {
element.onclick = function() {
setTimeout(() => {
console.log("called message handler")
callMessageHandler("reloadWebView")
}, 1000);
};
}
})();
You can use the userscript messageHandler functionality to reload the url, my example is incomplete but its not hard to work this out.
I hope this helps someone.
We were surprised we didn't find any mention of this anywhere online, so we're posting here in hopes we find a solution.
Using an iPhone with mobile safari is when we hit this issue running the 2 easy to follow tests below, one works, one doesn't.
Here is the link
https://pwa-react.netlify.com/
Here are the 2 tests we run (both listed in the link), one works when not in PWA mode, and the other fails when in PWA mode.
Test #1: Works Perfectly (Expected Behaviour)
Visit https://pwa-react.netlify.com/ from iPhone in mobile safari
1. Make sure you have google drive on the phone but not logged in.
2. Click "Choose File". It will show you the list of options to choose from.
3. Click "Browse" to look for the photo.
4. Click "Cancel" and you're back here.
5. Click "Choose File" it will still show you the list of options to choose from.
This works perfectly in mobile safari but NOT in PWA mode below.
Test #2: Does NOT Work (Unexpected Behaviour) (PWA)
Visit https://pwa-react.netlify.com/ from iPhone in mobile safari, hit the share
button, then add to home screen. This will add the PWA app on your phone. Open App.
1. Make sure you have google drive on the phone but not logged in.
2. Click "Choose File". It will show you the list of options to choose from.
3. Click "Browse" to look for the photo.
4. When it shows you the Google Drive logo with Sign In, double click the home
button, then go back to the PWA.
5. Click "Choose File" it will NOT show you the list of options to choose from.
This is now 100% broken.
The ONLY way to fix it is to go to Settings>Safari>Clear History and Website Data (all the way down)
How can we fix this so when the user hits "Choose File" it shows the list of
options to choose from in the PWA?
Screenshot #1: These are the options that appear in Test #1 and stop appearing in Test #2
Screenshot #2: This screen allows us to cancel in Test #1 but it disappears in Test #2
Any idea how to get Test #2 to work by allowing us to choose the upload options like in Screenshot #1 without breaking the app and having to go to safari settings to clear history and website data for it to function again?
PS - Here is the repository file pwa-react/src/App.js
We were facing almost exactly the same issue in our PWA, so first, off I want to thank you for helping us narrow down the cause.
After reviewing the iOS PWA lifecycle (article here) and a couple maddening hours of trial and error I was able to figure out a solution that is semi-acceptable.
My guess at what is happening when you leave the app mid-upload (Test #2) is that there is some internal context in how iOS handles PWA's that is not being reset, so when you go back and try to upload a file again it thinks that the upload dialog is already open.
The article mentions that opening external links without target=_blank will cause the PWA context to be deleted, so when the in-app browser closes, the PWA page reloads in the standalone window. I thought that might reset the "detached upload" context, and it ended up working.
So I created a page hosted on another domain, and linked to it below our upload button in the PWA:
// not sure the target={'_self'} is necessary but not risking it
<a href={'https://externalDomain.com/reset'} target={'_self'}>
Having Issues? Reset Upload
</a>
This works decently well, minus one issue. When you click this link it opens the in-app browser, but there is no "Done" button or navigation tools for the user to know how to exit. Linking back to the PWA does not work, because iOS detects that and does not reset the app context. What I did notice was that if I navigated to another page from the first external page (I originally just tested this with google.com), the "Done" button would show up, making it obvious how to exit.
With that knowledge, I guessed that you could probably just do window.history.pushState to achieve the same effect, which works. My final solution is below. It causes the entire app to reload when the user presses Done from the in-app browser, but that's far better than having them re-add to the home screen in my opinion.
const Reset: React.FC = props => {
React.useEffect(() => {
// Redirect any wayward users who find this page from outside the PWA
if (!window.matchMedia('(display-mode: standalone)').matches) {
navigate('/');
}
// push an additional page into history
const newUrl = `${window.location.href}?reset`;
window.history.pushState({ path: newUrl }, '', newUrl);
}, []);
return (
<Grid container>
<ArrowUpIcon />
<Typography variant={'h5'}>Press done above to return to App</Typography>
<Typography variant={'body1'}>Sorry for the inconvenience!</Typography>
</Grid>
);
};
Hope this helps! Would love to hear if it works for you.
Edit After Production Testing:
An additional important note is that your "reset" page must be on a completely different domain for this to work. Ran into this today in production, because our reset page was on a subdomain with the same root as the PWA, iOS was not resetting the entire PWA lifecycle.
SUMMARY
Key Issues:
Leaving an iOS PWA while any of the "file upload" dialogs are open ('Take Photo', 'Photo Library', or 'Browse') breaks the iOS PWA lifecycle.This breakage makes it impossible for the user to open any "file upload" dialogs when clicking on a file input.
In order to fix this issue, the PWA context must be completely reset.
It seems that the only ways to reset the PWA context are to restart the phone, delete the app and re-add it to the home screen, or to open an external link.
When opening an external link, the "Done" button that closes the iOS PWA embedded browser will not show on the initial page. The user must navigate to an additional external page in order for the "Done" button to show.
External links do not trigger a reset of the PWA context reset when they have target="_blank".
Solution:
In order for the user to be able to upload files again, the PWA context must be reset. The easiest way to do this (in my opinion) is to ask them to open an external link.
(In PWA): Present a link to the user to fix the fact that the upload dialog is not showing. The link destination must be a completely unrelated domain (not a subdomain) and must have target="_self" (issue #5).
(External Page): Once the user clicks on the link and the external page opens, there will be no visible way to leave the page (issue #4). To resolve this, you can use history.pushState to simulate navigating to an additional page.
(External Page - Bonus): To make it clear to the user that the issue has been resolved, add an arrow in the top left pointing to the "Done" button (as shown in my screenshot).
I am facing the following problem:
In a web interface, file downloads are triggered with an anchor tag, like this:
<a href="/bla/blabla" download>..</a>
While Safari browser can handle this request and open a dialogue to handle the file, WKWebView treats this just as an ordinary link and does nothing with it. I want to be able to get the file handler dialogue that is normally there when using Safari.
Right now there are 2 problems and I do not see an opening there yet:
I can not detect a click on the element as it is treated just as a normal link. And I can not rely on the URL parameters to detect if it is a file since that is not constantly true.
Even if the URL is defined as a one leading to a file, I can not pass it to Safari since it does not share session info and cookies with my app's WKWebView.
Therefore, I would like to know if there is any opening in handling files in iOS WKWebView. Thank you.
I have a button in my app that opens a link via safari. There are some tasks user does in web and finally returns to app on pressing a button in browser. what I need to do is to terminate app when safari opens so that when user comes back form browser, app inits without previous data.
That's how I open link:
let adrs = "mylink"
UIApplication.shared.openURL(NSURL(string: adrs) as! URL)
How can I achieve this?
Technically you can just exit the program (e.g., exit(0)). However, you should not do so, as iOS apps should not terminate on their own. Rather clean up your UI, e.g., remove all view controllers and create new ones.
We have an app that is meant to be invoked from the Safari via URL Scheme. Since the iOS 9 update we keep getting a "Open this page in appname?" dialog. Previous the update, the app would simply open from the Safari without any kind of dialog.
Is there any reason this is happening now and any way to avoid it?
To avoid the alert, you need to avoid print a page (html) between the tap of the user and the store, if you use a link to your servers and then a 302 should work. But if you need to do this from javascript there is no way no avoid the alert opening, Apple did this to prevent those spammy banner that with javascript opens the store. If you still need to use html+javascript before the store redirect there is a way force the app store to open and it's overwriting the location of the page, the alert will appear anyway during the transition. Try something like this
window.location = {deep-link};
setTimeout( function() {
window.location = {dummy-page}; // the faster the better
},10);
The bad news my friend about this workaround, is that it works in iphone 6 but not in <=5