Testing UIImagePicker - swift

I am trying to test a UiImagePicker I have used the record feature on Xcode to get most of my test but it fails to capture an element for confirming the picture that I want to select. Same thing happens with the other option "Which is cancel" I recall some way of getting a list of all the elements on a view or something to that effect. So my question how do I get a reference to an option in a ImagePicker object in a view.
I am building for iOS9 and running Xcode7.2
my current test looks like this
`
func testMenu(){
loginInApp(app) //Gets you past the login
app.buttons["LogoButton"].tap() //entry point for menuView
XCTAssert(app.buttons["BR"].exists)
let brButton = app.buttons["BR"]
brButton.tap()
let photosFromSheet = app.sheets["Where would you like to get photos from?"]
XCTAssert(photosFromSheet.exists)
photosFromSheet.staticTexts["Where would you like to get photos from?"].tap()
XCTAssert(photosFromSheet.collectionViews.buttons["Chose from Library"].exists && photosFromSheet.buttons["Cancel"].exists)
photosFromSheet.collectionViews.buttons["Chose from Library"].tap()
XCTAssert(app.tables/*#START_MENU_TOKEN#*/.buttons["Moments"]/*[[".cells[\"Moments\"].buttons[\"Moments\"]",".buttons[\"Moments\"]"],[[[-1,1],[-1,0]]],[0]]#END_MENU_TOKEN#*/.exists)
app.tables/*#START_MENU_TOKEN#*/.buttons["Moments"]/*[[".cells[\"Moments\"].buttons[\"Moments\"]",".buttons[\"Moments\"]"],[[[-1,1],[-1,0]]],[0]]#END_MENU_TOKEN#*/.tap()
XCTAssert(app.collectionViews.cells["Photo, Landscape, March 12, 2011, 4:17 PM"].exists)
app.collectionViews.cells["Photo, Landscape, March 12, 2011, 4:17 PM"].tap()
XCTAssert(app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).elementBoundByIndex(2).childrenMatchingType(.Other).element.exists)
// Here is where things get ambiguous and do not actually select anything when the tests run.
app.childrenMatchingType(.Window).elementBoundByIndex(0).childrenMatchingType(.Other).elementBoundByIndex(2).childrenMatchingType(.Other).element.tap()
brButton.tap()
photosFromSheet.buttons["Cancel"].tap()
app.buttons["LogoButton"].tap()
`

The UIImagePicker is apparently not hittable
so this is the workaround
If you see tap() in documentation it says
/*!
* Sends a tap event to a hittable point computed for the element.
*/
- (void)tap;
/*Sends a tap event to a hittable/unhittable element.*/
extension XCUIElement {
func forceTapElement() {
if self.hittable {
self.tap()
}
else {
let coordinate: XCUICoordinate = self.coordinateWithNormalizedOffset(CGVectorMake(0.0, 0.0))
coordinate.tap()
}
}
}
Now you can call as
button.forceTapElement()
This does not work on the 6S+ tho. Hopefully that will be resolved soon.

/*Sends a tap event to a hittable/unhittable element.*/
extension XCUIElement {
func forceTapElement() {
if self.isHittable {
self.tap()
}
else {
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx: 0.0, dy: 0.0))
coordinate.tap()
}
}
}
Swift 4 Version.

Related

Error "Connection to plugin interrupted/invalidated while in use" when switching to custom keyboard in swift application

I have been so frustrated for so long with this problem. My code was working just fine and then I took a break from the program for winter break. When I came back to it, whenever I tried to switch from the default keyboard to my custom keyboard, I got two error messages: "Connection to plugin interrupted while in use" and "Connection to plugin invalidated while in use"
After scouring the internet and trying one thing after the next to no avail, I resorted to commenting out code to see if a certain part was causing the problem and it was. I have a section that loops through a list of objects that have names that correspond to the exact name of the image files I want to use in my keyboard and the loop adds those images to the keyboard.
let emojisLocal = emojiStore.emojis
var xPos = 10
var yPos = 10
var count = 0;
for emoji in emojisLocal
{
if (count >= 5)
{
yPos += 70
}
let button = UIButton(type: UIButton.ButtonType.system) as UIButton
button.frame = CGRect(x: xPos, y: yPos, width: 40, height: 49)
if let image = UIImage(named: emoji.name)
{
button.setBackgroundImage(image, for: .normal)
}
button.backgroundColor = .white
view.addSubview(button)
count += 1
xPos += 50
}
I just don't understand because this code was working just fine not even a month ago and all of a sudden it just has stopped working. I even tried creating a new project and transferring all the code onto the new project and that gave the same errors. It's possible that I updated to Xcode version 13.1 between when it was working and when it stopped working, but I can't remember, could that be the problem? I know for sure that there is one item in the list when the program starts and that the name is correct.
Here's the code for the EmojiStore:
import Combine
class EmojiStore: ObservableObject
{
#Published var emojis : [EmojiListItem]
init (emojis: [EmojiListItem])
{
self.emojis = emojis
}
}
and for the EmojiListItem:
import Foundation
struct EmojiListItem : Codable, Identifiable
{
var id = UUID().uuidString
public var name : String
}
My post is similar to this one Plugin interrupted, invalidated errors for custom keyboard but the solution suggested on that post will not work for my program as I am not using UINibs.

Using swift built in partition to manage elements in an array

iOS 14, Swift 5.x
I watched this excellent WWDC from 2018
https://developer.apple.com/videos/play/wwdc2018/223/
And I wrote a shapes editor... and have been trying to use partition as Dave in the video says you should. I got the first three to work, but the last one I had to use a loop- cannot for the life of me figure out how to get it to work with partition.
Can someone see how I might do this?
The first method moves the selected object to the end of the list, works perfectly.
func bringToFrontEA() {
let subset = objects.partition(by: { $0.selected })
let selected = objects[subset...]
let unselected = objects[..<subset]
let reordered = unselected + selected
objects = Array(reordered)
}
The second method moves the selected object to the front of the list. Works prefectly.
func sendToBackEA() {
let subset = objects.partition(by: { !$0.selected })
let selected = objects[subset...]
let unselected = objects[..<subset]
let reordered = unselected + selected
objects = Array(reordered)
}
The third method moves the element just one element back in the list. Works perfectly.
func sendBackEA() {
if let i = objects.firstIndex(where: { $0.selected }) {
if i == 0 { return }
let predecessor = i - 1
let shapes = objects[predecessor...].partition(by: { !$0.selected })
let slice = objects[predecessor...]
let row = objects[..<predecessor]
let selected = Array(slice[..<shapes])
let unselected = Array(slice[shapes...])
objects = row + selected + unselected
}
}
The last method moves the element forward in the list, works perfectly... but unlike the other methods it will not scale as described in the WWDC video.
func bringForwardEA() {
let indexes = objects.enumerated().filter { $0.element.selected == true }.map{$0.offset}
for i in indexes {
if objects[i+1].unused {
return
}
objects.swapAt(i+1, i)
}
}
Objects is an array of shapes with a property indicating if it is selected or not. I want to exchange the loop in the last method by using a partition as I did in the first three. It needs to work for one or more selected shapes.
Looking at the WWDC video, it appears that what you are calling sendBackEA is what WWDC calls bringForward, and what you are calling bringForwardEA is what WWDC calls sendBack.
Just like how you move the first selected element forward one index (index decreases) in sendBackEA, then move all the other selected elements to immediately after that first selected element. bringForwardEA should do the reverse: move the last selected element backward one index (index increases), then move all the other selected elements to immediately before the last selected element. (See circa 19:10 in the video)
You seem to have confused yourself by trying to increase the indices of all the selected index by 1. This obviously cannot be done with a partition in general.
Also note that partition(by:) already modifies the collection, you don't need to get each partition, then recombine.
Your 4 methods can be written like this:
func bringToFrontEA() {
objects.partition(by: { $0.selected })
}
func sendToBackEA() {
objects.partition(by: { !$0.selected })
}
func sendBackEA() {
if let i = objects.indices.first(where: { objects[$0].selected }) {
if i == 0 { return }
let predecessor = i - 1
objects[predecessor...].partition(by: { !$0.selected })
}
}
func bringForwardEA() {
if let i = objects.indices.last(where: { objects[$0].selected }) {
if i == objects.indices.last { return }
let successor = i + 1
objects[...successor].partition(by: { !$0.selected })
}
}
Notice the symmetry between sendBackEA and bringForwardEA.

SKSpriteNode isn't responding when I try to hide it (Spoiler: SKAction timing issue)

OK, so I've been working on doing an RPG-style dialog box for a project, and while most of it is going smoothly, the one thing that's tripping me up right now is the little icon in the corner of the box to let you know there's more.
I tried to figure out how to get draw the shape, but not having any luck getting Core Graphics to draw triangles I decided to just use a PNG image of one instead. The code below shows everything relevant to how it's been set up and managed.
That being figured out, I'm now trying to get it to hide the marker when updating the box and show it again afterward. Here's what I've tried so far:
Method 1: Use .alpha = 0 to hide it from view during updates, restore with .alpha = 1
Method 2: Remove it from the node tree
Method 3: Place it behind the box background (located at .zPosition = -1)
The result has been consistent across all 3 methods: The triangle just stays in place, unresponsive when invoked.
class DialogBox: SKNode {
private var continueMarker = SKSpriteNode(imageNamed: "continueTriangle") // The triangle that shows in the lower-right to show there's more to read
init() {
/// Setup and placement. It appears in the proper position if I draw it and don't try to hide anything
continueMarker.size.width = 50
continueMarker.size.height = 25
continueMarker.position = CGPoint(x: ((width / 2) - (continueMarker.size.width * 0.9)), y: ((continueMarker.size.height * 0.9) - (height - margin)))
addChild(continueMarker)
}
func updateContent(forceAnimation: Bool = false) {
/// Determine what content to put into the box
hideContinueMarker()
/// Perform the content update in the box (which works as it should)
showContinueMarker()
}
func showContinueMarker() {
// continueMarker.alpha = 1 /// Method 1: Use .alpha to hide it from view during updates
// if (continueMarker.parent == nil) { // Method 2: Remove it from the tree
// addChild(continueMarker)
// }
continueMarker.zPosition = -2 /// Method 3: place it behind the box background (zPosition -1)
}
func hideContinueMarker() {
// continueMarker.alpha = 0 /// Method 1
// if (continueMarker.parent != nil) { /// Method 2
// continueMarker.removeFromParent()
// }
continueMarker.zPosition = 2 /// Method 3
}
}
OK, so while typing this one up I had some more ideas and ended up solving my own problem, so I figured I'd share the solution here, rather than pull a DenverCoder9 on everyone.
On the plus side, you get a look at a simple way to animate text in SpriteKit! Hooray!
In a final check to make sure I wasn't losing my mind, I added some print statements to showContinueMarker() and hideContinueMarker() and noticed that they always appeared simultaneously.
What's that mean? SKAction is likely at fault. Here's a look at the code for animating updates to the box:
private func animatedContentUpdate(contentBody: String, speaker: String? = nil) {
if let speaker = speaker {
// Update speaker box, if provided
speakerLabel.text = speaker
}
var updatedText = "" // Text shown so far
var actionSequence: [SKAction] = []
for char in contentBody {
updatedText += "\(char)"
dialogTextLabel.text = updatedText
// Set up a custom action to update the label with the new text
let updateLabelAction = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self, updatedText] (node, elapsed) in
self?.dialogTextLabel.text = updatedText
})
// Queue up the action so we can run the batch afterward
actionSequence.append(updateLabelAction)
}
/// HERE'S THE FIX
// We needed to add another action to the end of the sequence so that showing the marker again didn't occur concurrent with the update sequence.
let showMarker = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self] (node, elapsed) in
self?.showContinueMarker()
})
// Run the sequence
actionSequence.append(showMarker)
removeAction(forKey: "animatedUpdate") // Cancel any animated updates already in progress
run(SKAction.sequence(actionSequence), withKey: "animatedUpdate") // Start the update
}
In case you missed it in the big block there, here's the specific bit in isolation
let showMarker = SKAction.customAction(withDuration: animateUpdateSpeed.rawValue, actionBlock: { [weak self] (node, elapsed) in
self?.showContinueMarker()
})
Basically, we needed to add showing the triangle as an action at the end of the update sequence instead of just assuming it would occur after the update since the function was invoked at a later time.
And since all 3 methods work equally well now that the timing has been fixed, I've gone back to the .alpha = 0 method to keep it simple.

UITextField being treated as nil when it isn't

I have a piece of code intended to allow a user to change which unit of weight they are using. If they've already entered a value it is automatically converted between kilograms and pounds. It is changed by tapping on a UISegmentedControl where segment 0 is kilogram and segment 1 is pound.
Changing the first time works perfectly regardless of if the user went from kilogram to pound or vice versa. However, upon attempting to make a second conversion the program immediately crashes claiming that the value in the text field containing weight is nil even though it clearly contains text.
Here is my code as of now:
#IBAction func unitChanged(_ sender: UISegmentedControl) {
if let text = weightInput.text, text.isEmpty {
//Does nothing since there is no weight to change the unit of
}
else {
let weight: Int? = Int(weightInput.text!)
var weightDouble: Double = Double(weight!)
if (weightTypeInput.selectedSegmentIndex == 0) {
//The user is trying to change from pounds to kilograms
weightDouble = weightDouble * 0.45359237
}
else {
//The user is trying to change from kilograms to pounds
weightDouble = weightDouble * 2.2046226218
}
weightInput.text = String(weightDouble)
}
}
Here is what the code looks like in practice
Finally here is the error message I get:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
If I put a default value for weightDouble it is always used for all subsequent conversion attempts. Has anyone here encountered a similar issue? I attempted to search online but I couldn't find anything to help.
Are you sure that the text value of your TextField is nil? Since you're simply checking it in the first if in my opinion that would not be the case.
Int can't parse numbers with a floating-point in them. It returns nil when you do this. You might want to parse it into a Double first and then convert it to Int.
I think your code should be as follows:
#IBAction func unitChanged(_ sender: UISegmentedControl) {
guard let text = weightInput.text, !text.isEmpty, var weightDouble = Double(text) else { return }
if (weightTypeInput.selectedSegmentIndex == 0) {
//The user is trying to change from pounds to kilograms
weightDouble = weightDouble * 0.45359237
}
else {
//The user is trying to change from kilograms to pounds
weightDouble = weightDouble * 2.2046226218
}
weightInput.text = String(Int(weightDouble))
}

In XCUITests, how to wait for existence of either of two ui elements

Looking at XCTWaiter().wait(...) I believe we can wait for multiple expectations to become true using this code
let notHittablePredicate = NSPredicate(format: "hittable == false")
let myExpectation = XCTNSPredicateExpectation(predicate: notHittablePredicate, object: element)
let result = XCTWaiter().wait(for: [myExpectation], timeout: timeout)
//for takes array of expectations
But this uses like AND among the supplied expectations. Is there a way to do OR among the supplied expectations.
Like i have a use case at login that after tapping submit, i want to wait for one of two elements. First element is "You are already logged in on another device. If you continue any unsaved data on your other device will be lost?". And second element is the main screen after login. So any one can appear. Currently I'm first waiting for first element until timeout occurs and then for the second element. But I want to optimize time here and move on as soon as any of two elements exist==true. Then i'll check if element1 exists then tap YES and then wait for main screen otherwise just assert existence of element2.
Please comment if something isn't clear in the question. Thanks
Inspired by http://masilotti.com/ui-testing-tdd/, you don't have to rely on XCTWaiter. You can simply run a loop and test whether one of them exists.
/// Waits for either of the two elements to exist (i.e. for scenarios where you might have
/// conditional UI logic and aren't sure which will show)
///
/// - Parameters:
/// - elementA: The first element to check for
/// - elementB: The second, or fallback, element to check for
/// - Returns: the element that existed
#discardableResult
func waitForEitherElementToExist(_ elementA: XCUIElement, _ elementB: XCUIElement) -> XCUIElement? {
let startTime = NSDate.timeIntervalSinceReferenceDate
while (!elementA.exists && !elementB.exists) { // while neither element exists
if (NSDate.timeIntervalSinceReferenceDate - startTime > 5.0) {
XCTFail("Timed out waiting for either element to exist.")
break
}
sleep(1)
}
if elementA.exists { return elementA }
if elementB.exists { return elementB }
return nil
}
then you could just do:
let foundElement = waitForEitherElementToExist(elementA, elementB)
if foundElement == elementA {
// e.g. if it's a button, tap it
} else {
// element B was found
}
lagoman's answer is absolutely correct and great. I needed wait on more than 2 possible elements though, so I tweaked his code to support an Array of XCUIElement instead of just two.
#discardableResult
func waitForAnyElement(_ elements: [XCUIElement], timeout: TimeInterval) -> XCUIElement? {
var returnValue: XCUIElement?
let startTime = Date()
while Date().timeIntervalSince(startTime) < timeout {
if let elementFound = elements.first(where: { $0.exists }) {
returnValue = elementFound
break
}
sleep(1)
}
return returnValue
}
which can be used like
let element1 = app.tabBars.buttons["Home"]
let element2 = app.buttons["Submit"]
let element3 = app.staticTexts["Greetings"]
foundElement = waitForAnyElement([element1, element2, element3], timeout: 5)
// do whatever checks you may want
if foundElement == element1 {
// code
}
NSPredicate supports OR predicates too.
For example I wrote something like this to ensure my application is fully finished launching before I start trying to interact with it in UI tests. This is checking for the existence of various landmarks in the app that I know are uniquely present on each of the possible starting states after launch.
extension XCTestCase {
func waitForLaunchToFinish(app: XCUIApplication) {
let loginScreenPredicate = NSPredicate { _, _ in
app.logInButton.exists
}
let tabBarPredicate = NSPredicate { _, _ in
app.tabBar.exists
}
let helpButtonPredicate = NSPredicate { _, _ in
app.helpButton.exists
}
let predicate = NSCompoundPredicate(
orPredicateWithSubpredicates: [
loginScreenPredicate,
tabBarPredicate,
helpButtonPredicate,
]
)
let finishedLaunchingExpectation = expectation(for: predicate, evaluatedWith: nil, handler: nil)
wait(for: [finishedLaunchingExpectation], timeout: 30)
}
}
In the console while the test is running there's a series of repeated checks for the existence of the various buttons I want to check for, with a variable amount of time between each check.
t = 13.76s Wait for com.myapp.name to idle
t = 18.15s Checking existence of "My Tab Bar" Button
t = 18.88s Checking existence of "Help" Button
t = 20.98s Checking existence of "Log In" Button
t = 22.99s Checking existence of "My Tab Bar" Button
t = 23.39s Checking existence of "Help" Button
t = 26.05s Checking existence of "Log In" Button
t = 32.51s Checking existence of "My Tab Bar" Button
t = 16.49s Checking existence of "Log In" Button
And voila, now instead of waiting for each element individually I can do it concurrently.
This is very flexible of course, since you can add as many elements as you want, with whatever conditions you want. And if you want a combination of OR and AND predicates you can do that too with NSCompoundPredicate. This can easily be adapted into a more generic function that accepts an array of elements like so:
func wait(for elements: XCUIElement...) { … }
Could even pass a parameter that controls whether it uses OR or AND.
Hey other alternative that works for us. I hope help others too.
XCTAssert(
app.staticTexts["Hello Stack"]
.waitForExistence(timeout: 10) || app.staticTexts["Hi Stack"]
.waitForExistence(timeout: 10)
)