Xcode UI Testing - typing text with typeText() method and autocorrection - uitextfield

I've got a test like below:
let navnTextField = app.textFields["First Name"]
let name = "Henrik"
navnTextField.tap()
navnTextField.typeText("Henrik")
XCTAssertEqual(navnTextField.value as? String, name)
Problem is that by default my iPhone Simulator has got Polish keyboard because of the system settings and "Henrik" is automatically changed into "ha" by autocorrect.
Simple solution is to remove Polish keyboard from the iOS Settings. This solution however is not solving the problem because iPhone Simulator can be reset and then test will fail again.
Is there any way to setup autocorrect before test case or other way to input text to text field.

Here's a small extension on XCUIElement to accomplish this
extension XCUIElement {
// The following is a workaround for inputting text in the
//simulator when the keyboard is hidden
func setText(text: String, application: XCUIApplication) {
UIPasteboard.generalPasteboard().string = text
doubleTap()
application.menuItems["Paste"].tap()
}
}
It can be used like this
let app = XCUIApplication()
let enterNameTextField = app.otherElements.textFields["Enter Name"]
enterNameTextField.tap()
enterNameTextField.setText("John Doe", app)
Credit goes to #Apan for the implementation

There is a workaround to use UIPasteboard to provide input text:
let navnTextField = app.textFields["First name"]
navnTextField.tap()
UIPasteboard.generalPasteboard().string = "Henrik"
navnTextField.doubleTap()
app.menuItems.elementBoundByIndex(0).tap()
XCTAssertEqual(navnTextField.value as? String, name)
You can check link with description as a workaround for secure input in GM
Edit
For better readability instead app.menuItems.elementBoundByIndex(0).tap()
you can do app.menuItems["Paste"].tap().

Currently using Swift 4 on Xcode 10
you can now use typeText(String) like this
let app = XCUIApplication()
let usernameTextField = app.textFields["Username"]
usernameTextField.typeText("Caseyp")

For swift v3 need use new sintax (answer by #mike):
extension XCUIElement {
func setText(text: String?, application: XCUIApplication) {
tap()
UIPasteboard.general.string = text
doubleTap()
application.menuItems.element(boundBy: 0).tap()
}
}
and use it:
let app = XCUIApplication()
let enterNameTextField = app.otherElements.textFields["Enter Name"]
enterNameTextField.tap()
enterNameTextField.setText(text: "John Doe", application: app)

Tweaked:
so extension is on application which makes a bit more sense to me
the existing field contents are emptied
code:
extension XCUIApplication {
// The following is a workaround for inputting text in the
//simulator when the keyboard is hidden
func setText(_ text: String, on element: XCUIElement?) {
if let element = element {
UIPasteboard.general.string = text
element.doubleTap()
self.menuItems["Select All"].tap()
self.menuItems["Paste"].tap()
}
}
}
Run with:
self.app?.setText("Lo", on: self.app?.textFields.firstMatch)

Related

SwiftUI - How to get access to "WindowScene"

In watching the WWDC 21 videos reference StoreKit 2, there are a few functions that they reference wherein they let a value = WindowScene as follows:
func manageSubscriptions() async {
if let windowScene = self.view.window?.windowScene {
do {
try await AppStore.showManageSubscriptions(in: windowScene)
} catch {
//error
}
}
}
The let line errors out with the message: Type of expression is ambiguous without more context
If I try and provide more context with something like:
if let windowScene = (self.view.window?.windowScene)! as UIWindowScene {
I am told: Value of type 'MyStruct' has no member 'view'
What am I missing, must be something simple, to gain access to this needed UI element?
Thank you
Added:
I'd like to add that I am using a SwiftUI app that was created using a SceneDelegate and AppDelegate, not a simple struct: App, type of structure. So I am guessing I need to access something in the SceneDelegate to get the right object..
Just to provide an answer for anyone interested, with all credit to #aheze for finding it and #Matteo Pacini for the solution, to get this specific method to work when using a SwiftUI app that has an AppDelegate/SceneDelegate structure, this will work:
#MainActor
func manageSubscriptions() async {
if let windowScene = UIApplication.shared.connectedScenes.first {
do {
try await AppStore.showManageSubscriptions(in: windowScene as! UIWindowScene)
} catch {
//error
}
}
}
You can conversely use the view modifier manageSubscriptionsSheet(isPresented:) instead. This is Apple's recommended approach when using SwiftUI and will mitigate the need for getting a reference to the window scene.
Source:
If you’re using SwiftUI, call the manageSubscriptionsSheet(isPresented:)view modifier.

Swift 4.2.1 requesting JSON with Xcode 10.1

My code:
let cgpurl = URL(string: "https://api.coingecko.com/api/v3/ping")!
let task = URLSession.shared.dataTask(with: cgpurl) { (Data, URLResponse, Error) in
if let data = Data, let string = String(data: data, encoding: .utf8) {
let CGPing = string } ; resume() }
The problem is with the 2nd use of "cgpurl". I've tried changing case to no effect. The error I'm getting is, "Cannot use instance member 'cgpurl' within property initializer; property initializers run before 'self' is available". Ok... but I can't even replace cgpurl with the actual link? Then I get the error message "Ambiguous reference to member 'dataTask(with:completionHandler:)'" I realize this release of swift was supposed to be "small" & just to "fix errors" but I've not been able to find any current documentation on this release. I'm using swift 4.2.1 with Xcode 10.1
This code was taken directly from a teaching manual for Swift 4.2
No, it wasn't. The code you have was never right, in Swift 4.2 or any other version of Swift. You have blindly copied and pasted perhaps, without looking at the overall context.
The problem is that the code, as you have it, is sitting "loose" at the top of your view controller or other class declaration, perhaps something along these lines:
class MyViewController : UIViewController {
let cgpurl = // ...
let task = // ...
}
That's wrong. The most basic rule of Swift programming is that executable code can exist only in a function. For example:
class MyViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cgpurl = // ...
let task = // ...
}
}
That may not solve all your issues, but at least you'll get past the most basic mistake you're making and the "Cannot use instance member" compile error will go away.

Distinguishing first launch on UI Tests

I want to use Fastlane Snapshot in order to generate screenshots for my app. But the behavior of the app is different when launch for the first time and launching after that.
How do I manage to get a consistent behavior in order to grab the screenshots?
(this question is also relevant for any UI test desired consistency I presume)
You should be using UserDefaults class to preserve data in your app so that you can stub data in your tests.
If we assume that the Bool key you use to see if it's the first launch is isFirstTime, in order to stub it in your UI test, you should pass it to launchArguments following the value YES or NO (for Bool values). Note that I added - sign before key, this is the way it works:
class FirstTimeLaunchTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "YES"]
app.launch()
}
func testWelcomeScreenShown() {
XCTAssert(app.navigationBars["Welcome"].exists)
}
}
For tests where you'd like to have first start skipped, use this class:
class LaterLaunchesTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "NO"]
app.launch()
}
func testMainAppScreenShown() {
XCTAssert(app.navigationBars["My App"].exists)
}
}
One note though: If you are using SwiftyUserDefaults library, this solution wouldn't work. There is a problem in the current version of the library where converting YES and NO strings to true and false is not working as expected. There was a PR that solved this problem (that is rejected), but to solve this problem, you can look at the solutions #2 and #3 from this answer.

Quick use of MASShortcut framework on Swift project

I am having trouble implementing MASShortcut (docs here) in a Swift OSX project to listen for global hotkeys. I have managed to import the framework via CocoaPods, and I have a working MASShortcutView instance:
#IBOutlet weak var testShortcutView:MASShortcutView!
I also figured out how to monitor and trigger something with the shortcut (please tell me if this is correct):
let shortcut = MASShortcut(keyCode: keycode, modifierFlags: modifierkeys)
MASShortcutMonitor.sharedMonitor().registerShortcut(shortcut, withAction: callback)
The question here is, how can I get the keyCode and modifierFlags from my MASShortcutView?
I really thank you in advance, I searched everywhere and I can't find an example on how to do this on swift. All I can find is is objective-c, and I can't figure it out.
Following code will register shortcut handler for Cmd+Shift+K key combination
let shortcut = MASShortcut.init(keyCode: UInt(kVK_ANSI_K), modifierFlags: UInt(NSEventModifierFlags.CommandKeyMask.rawValue + NSEventModifierFlags.ShiftKeyMask.rawValue))
MASShortcutMonitor.sharedMonitor().registerShortcut(shortcut, withAction: {
print("Hello world")
})
Cmd and Shift - modifing keys. You should set them in the modifierFlags parameters. Full list of possible values is available in NSEventModifierFlags enum.
For your convenience I have placed sample project on github:
https://github.com/melifaro-/ShortCutSwiftSample
That handles shortcuts changes:
shortcutView.shortcutValueChange = { (sender) in
let callback: (() -> Void)!
if self.shortcutView.shortcutValue.keyCodeStringForKeyEquivalent == "k" {
callback = {
print("K shortcut handler")
}
} else {
callback = {
print("Default handler")
}
}
MASShortcutMonitor.sharedMonitor().registerShortcut(self.shortcutView.shortcutValue, withAction: callback)
}
I have pushed changes into the repo. I would recommend to try following scenario:
Using the shortcut view:
Set Cmd+Shift+K shortcut
Set Cmd+Shift+J shortcut
Try this shortcuts - different callbacks should be performed
Hope it helps.
In Swift 5
let shortcut = MASShortcut(keyCode: kVK_ANSI_K, modifierFlags: [.command, .shift])
MASShortcutMonitor.shared()?.register(shortcut, withAction: {
print("hello")
})

How to associate existing file type for Mac OS application in XCode 7?

I am making a simple viewer for jpeg pictures in Xcode 7, and trying to associate Jpeg file type with my application. This is a Cocoa application for OS X in Swift that uses storyboards, and is not a document-based application.
Tutorials that I found online suggest going through Info tab, adding a new document type there. Now that's where things start to look different from those in tutorials: in them there is only 3 field to fill out (Name, Types, Icon), but I have many more. I tried to experiment around and here is what I put in fields that I got:
Name: JPEG image
Class: left it blank
Extensions: .jpg, .jpeg, .JPEG, .JPG
Icon: left it blank
Identifier: public.jpeg
Role: Viewer
Mime types: image/jpeg
"Document is distributed as a bundle" is partially checked by default; I just left it as is.
Did not touch additional document type properties either.
As a result I got my application showing in a list in "Open with" when I secondary-click a Jpeg file along with other installed applications, but once I try to open it that way, I get a popup saying The document "picture.jpg" could not be opened. MyApp cannot open files in the "JPEG image" format.
What am I doing wrong?
The Class field is compulsory, and you have to have corresponding implementation.
Try:
Class: $(PRODUCT_MODULE_NAME).Document
And then add the Document.swift:
import Cocoa
class Document : NSDocument
{
override init() {
super.init()
// Add your subclass-specific initialization here.
}
override class func autosavesInPlace() -> Bool {
return true
}
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
}
override func data(ofType typeName: String) throws -> Data {
// Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil.
// You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
override func read(from data: Data, ofType typeName: String) throws {
// Insert code here to read your document from the given data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning false.
// You can also choose to override readFromFileWrapper:ofType:error: or readFromURL:ofType:error: instead.
// If you override either of these, you should also override -isEntireFileLoaded to return false if the contents are lazily loaded.
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
^ Modify as you need.
Also, you specified the role "Viewer", means you can open it with the space bar, and not the double click (open) - which is the role "Edit" right?

Resources