How to do 'page' object design in XCUITest - swift

I'm new to XCUITest. I would like to get some suggestions on how to design 'page' (actually more like screen) object XCUITest.
Is there any open-source project that give samples to reference?
I'm thinking to create a screen folder to put all the screen objects there to hold methods and locators for each screen. Create a test folder to put the actual test cases. And also create a lib folder to put the common methods there.
I was wondering 2 things: 1. How XCUITest reference the files in different folders, how can one test file import methods from 2 different screen files? 2. I'm also thinking to create a data file (acting like .json file). Should I set it as .swift file? and how to make my test call that?
many thanks!

You need not import files from different folders which reside in a same target. They can be used directly without importing.
Refer Reading in a JSON File Using Swift to read from json files.
For page object design
AppNameUITests/Screens/Screen1.swift
class Screen1 {
var app: XCUIApplication
init(app: XCUIApplication) {
self.app = app
}
var element1: XCUIElement {
return app.buttons.firstMatch
}
var element2: [XCUIElement] {
return app.textFields.allElementsBoundByIndex
}
func method1() -> Int {
element1.click()
return element2.count
}
}
AppNameUITests/Tests/TestSuite1.swift
class TestSuite1: XCTestCase {
override func setUp() {
// launch application and pre-conditions for the test case
}
override func tearDown() {
// clean up steps and terminate the application
}
func testMethod1() {
let app = XCUIApplication()
let screen1Obj = Screen1(app: app)
XCTAssertTrue(screen1Obj.method1() == 5, "Test failed.")
}
}

Related

Objects with application-wide scope in Swift Vapor

How can I create objects within a Vapor-Applicaton that are instantiated when the app starts and are acessible from within my controllers?
I would like to use Dictionary to store some of my models across multiple requests and assign them to a user via hidden fields.
Unfortunately SessionData only accepts Strings.
Many thanks in advance
Michael
The obvious way is to extend Application. I used the documentation for Repositories as inspiration. First, create a structure to hold your properties:
struct AppConfig {
var emailStatus: EmailStatus
static var environment: AppConfig {
return .init(emailStatus: .unknown)
}
}
Then extend:
extension Application {
struct AppConfigKey: StorageKey {
typealias Value = AppConfig
}
var config: AppConfig {
get {
storage[AppConfigKey.self] ?? .environment
}
set {
storage[AppConfigKey.self] = newValue
}
}
}
Finally, initialise in configure.swift:
app.config.emailStatus = .unknown
I’ve used an enumeration as an example but it can be whatever you want.
Edit: Addressing OP's issues and concerns
I put the above code in a separate source file, so you need:
import Vapor
To gain access to StorageKey, etc.
Accessing the running application instance in a controller is easy, it's available from the request as in request.application.

How to handle crashes in unit test target in Mac OS application

In application we can register the below set of code to catch the crashes
signal(SIGILL) { _ in
print("Signal Kill")
}
ncaughtExceptionHandler { exception in
print("Exception caught: \(exception)")
}
But I want to achieve this in Unit test target as well. How can I do this?
If you need to have the signal handlers to be installed before the unit tests run, you can create a .m file similar to this:
// setup.m
static void __attribute__((constructor)) setup() {
// switch to Swift as soon as possible :)
[MySignalHandlers installAllHandlers];
}
, and then create a Swift class that does the actual work. Ofcourse you could write the signal handlers in Objective-C, but since you already have them in Swift, you can reuse that logic.
// MySignalHandlers.swift
class MySignalHandlers: NSObject {
#objc static func installAllHandlers() {
// do your thing here
}
}
Functions decorated with __attribute__((constructor)) are automatically executed when the binary that holds them is loaded, so this guarantees that the signal handlers are installed before any unit tests run.

Best practices in protractor for big pageObject files

In my previous protractor JS project (This new one I will do it with TS) I created one class for all my elements and another one for my functions, something like this:
specs
|_reportPage
|_lib
|_pageElements.js
|_pageFunctions.js
Then I was importing the files as necessary, in this way was easy to find the info since the element list was long.
So far all examples online for protractor TS projects are short pageObject files with a couple of elements and methods, but I would like to know how to correctly proceed when the page requires a lot of elements and functions/methods.
For example, lets say we have 5 specs under the same folder that test the same page and this page is full of fields and tables.
What would be the best practice here? create 1 pageobject for each spec, create one long class with all the elements and functions...?
Thanks for your time!
To Extend my answer you can add additional layer as a service which can execute several actions from the flow in different pages.
Code example:
export class E2EService {
mainPage: MainPage = new MainPage();
innerPage: InnerPage = new InnerPage();
doSomethingE2E() {
this.mainPage.headerPage.weDoSomething();
this.mainPage.contentPage.weDoSomething()
this.innerPage.somethingComplicated();
}
}
export class MainPage {
public readonly headerPage: HeaderPage;
public readonly contentPage: ContentPage;
}
export class InnerPage {
headerPage: InnerHeaderPage;
contentPage: InnerContentPage;
public somethingComplicated() {
this.headerPage.weDoSomething();
this.contentPage.weDoSomething();
}
}
export class ContentPage {
private readonly elements = {
// elements
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
export class HeaderPage {
private readonly elements = {
btn1: element(by.id('')),
div: element(by.id('')),
h2: element(by.id(''))
};
public weDoSomething() {
// code
}
public getElements() {
return this.elements;
}
}
Based on Infern0's answer, I did dependency injection to the classes:
class HeaderElements {
foo = element(by.id("foo"));
//List goes on...
}
class HomePageElements {
foo = element(by.id("foo"));
//List goes on...
}
export class MainCommonElementsPage {
headerElements: HeaderElements;
homePageElements: HomePageElements;
constructor() {
this.headerElements = new HeaderElements();
this.homePageElements = new HomePageElements();
}
}
Best practices, even for large Page Objects is this:
Each page should only have 1 page object class. All of the tools needed to access that page should be located here. Think of your page object as an API.
Don't break the PO into different parts, especially for large pages. You'll eventually need to modify the PO to adjust for content changes. Would you rather change 1 file or 12? This also ensures that each of your e2e tests will remain functional after you update the PO.
I have one PO that handles a page with a lengthy form. The form has 12 controls and three buttons (cancel, reset, and submit). I have about 30 functions that deal with the form. I don't like having more than 1-2 methods in my test, so if it gets more complicated, I add to the PO.

How to correctly extend an existing MVVM UI component?

In my Kotlin desktop application using TornadoFX, I have created an AudioCard layout (subclass of VBox) which has a few labels and basic audio player controls. This AudioCard has an AudioCardViewModel which handles events from the UI and an AudioCardModel which holds information like the title, subtitle, audio file path, etc. A simplified version is shown below.
data class AudioCardModel(
var title: String,
var audioFile: File
)
class AudioCardViewModel(title: String, audioFile: File) {
val model = AudioCardModel(title, audioFile)
var titleProperty = SimpleStringProperty(model.title)
fun playButtonPressed() {
// play the audio file from the model
}
}
class AudioCard(title: String, audioFile: File) : VBox() {
val viewModel = AudioCardViewModel(title, audioFile)
init {
// create the UI
label(title) {
bind(viewModel.titleProperty)
}
button("Play") {
viewModel.playButtonPressed()
}
}
}
Up until this point, I have tried to keep the code as general as possible, allowing myself or others to reuse this UI component in future applications that need to play audio. However, for my current application, it makes the most sense to have a more specialized version of this UI component that initializes itself directly from my data model class and can extend some of the actions. I've tried something like this (the required fields and classes from the previous code block were switched to open):
data class CustomAudioCardModel(
var customData: CustomData
)
class CustomAudioCardViewModel(customData: CustomData)
: AudioCardViewModel(customData.name, customData.file) {
val model = CustomAudioCardModel(customData)
override fun playButtonPressed() {
super.playButtonPressed()
// do secondary things only needed by CustomAudioCardViewModel
}
}
class CustomAudioCard(customData: CustomData): AudioCard(customData.name, customData.file) {
override val viewModel = CustomAudioCardViewModel(customData)
}
Unfortunately, this isn't so straightforward. By overriding viewModel in CustomAudioCard, the viewModel property ceases to be final, causing a NullPointerException when the init function of the AudioCard superclass tries to use the view model to set up the title label before the child class has initialized the view model.
I suspect there might be a way out of this by defining an AudioCardViewModel interface and/or using Kotlin's ability to delegate with the by keyword, but I'm under the impression that defining the interface (like in MVP) shouldn't be necessary for MVVM.
To summarize: What is the correct way to extend an existing MVVM control, specifically in the context of the Kotlin TornadoFX library?
Here is the solution I came across from Paul Stovell. Instead of creating the view model within the view (Option 1 in Stovell's article), I switched to injecting the view model into the view (Option 2). I also refactored for better MVVM adherence with help from the TornadoFX documentation and this answer regarding where business logic should go. My AudioCard code now looks like this:
open class AudioCardModel(title: String, audioFile: File) {
var title: String by property(title)
val titleProperty = getProperty(AudioCardModel::title)
var audioFile: File by property(audioFile)
val audioFileProperty = getProperty(AudioCardModel::audioFile)
open fun play() {
// play the audio file
}
}
open class AudioCardViewModel(private val model: AudioCardModel) {
var titleProperty = bind { model.titleProperty }
fun playButtonPressed() {
model.play()
}
}
open class AudioCard(private val viewModel: AudioCardViewModel) : VBox() {
init {
// create the UI
label(viewModel.titleProperty.get()) {
bind(viewModel.titleProperty)
}
button("Play") {
viewModel.playButtonPressed()
}
}
}
The extension view now looks like:
class CustomAudioCardModel(
var customData: CustomData
) : AudioCardModel(customData.name, customData.file) {
var didPlay by property(false)
val didPlayProperty = getProperty(CustomAudioCardModel::didPlay)
override fun play() {
super.play()
// do extra business logic
didPlay = true
}
}
class CustomAudioCardViewModel(
private val model: CustomAudioCardModel
) : AudioCardViewModel(model) {
val didPlayProperty = bind { model.didPlayProperty }
}
class CustomAudioCard(
private val viewModel: CustomAudioCardViewModel
) : AudioCard(customViewModel) {
init {
model.didPlayProperty.onChange { newValue ->
// change UI when audio has been played
}
}
}
I see a few ways to clean this up, especially regarding the models, but this option seems to work well in my scenario.

Play! 2.0 Scala - Accessing global object

I've declared an object which gets instantiated on application start. I want to access it inside a controller, which is part of a plugin. I want to be able to use that plugin, but I can't seem to get past the first part -- finding the MyWebsocketConnection object. None of the examples show how to do this. I don't want to inject into the controller because I'm writing a plugin (I saw static examples of how to do that somewhere).
Global.scala, plugin application \app\Global.scala
object Global extends GlobalSettings {
object MyWebsocketConnection {
val logger = // return something that gets instantiated once, like websocket object for logging to ui
}
class MyWebsocketConnection {
import MyWebsocketConnection.logger
}
override def onStart(app: Application) {
Logger.info("Application has started...");
}
}
My custom logging plugin controller:
MyLogger.Scala, plugin application \app\controllers\MyLogger.scala
object MyLogger {
def info(message: String) = {
// THIS CAN'T BE FOUND ?
// MyWebsocketConnection.logger.send(message)
}
}
So, from the Play! 2.0 app that references the plugin, I would (probably) do something like below, but I can't even get past the part before this:
MyFutureController.scala, another Play! application \app\controllers\MyFutureController.scala
object MyFutureController extends Controller {
def someRandomMethod = Action {
// Custom logging
MyLogger.info("Here's my log message!");
Ok("This documentation stinks!")
}
}
There is also workaround #3: move your Global class to a package and specify its fully qualified name in application.conf file, like so:
global= my.packaged.Global
The problem is that your Global objects resides in default package. And in Java, classes from default package can't be referenced from other packages, they are accessible only within the same package (default).
I see two workarounds of this problem.
Move MyWebsocketConnection to some named package (say config) so it can be accessible in your application.
object MyLogger {
def info(message: String) = {
config.MyWebsocketConnection.logger.send(message)
}
}
Move your whole application into single package (but it is a lot of pain)
foo
|--controllers
|--models
|--views
|--Global.scala
Then Global object will resides in foo package and will be accessible within application.