Automate an iOS Tic Tac Toe app with Appium 2 (XCUITest) Part 1: Player vs Player

How to use Appium 2 to automate playing Tic Tac Toe

Courtney Zhan
6 min readFeb 19, 2023

In this article, I’ll show you how to automate Nathan Fallet and Group MINASTE’s Tic Tac Toe iOS app with Appium 2.

Table of Contents:
·
Setup
Install Appium 2 and the XCUITest Driver
Build the Tic Tac Toe app
·
Get Started
How to Play Tic Tac Toe
How to Play Tic Tac Toe with Appium
Refactoring
·
Complete Test Scripts
Setup

Install Appium 2 and the XCUITest Driver

Follow the steps in my other article to install the Appium 2 server, the XCUITest Driver and your desired Appium Client library (I prefer Ruby).

A basic summary of the setup (for macOS) is as follows:

# Install Node.js
brew install node@18

# Install Appium 2
npm install -g appium@next

# Install XCUITest Driver
appium driver install xcuitest

# Run this in a separate window to start up the Appium server
appium

# Install Ruby's Appium client library
gem install --no-document appium_lib

Note that there are some security permissions that may need to be granted. See the article for detailed step-by-step instructions as well as a simple script to verify the setup.

Build the Tic Tac Toe app

In this exercise, I will run a Tic Tac Toe app on the iOS simulator, using the open-source tic tac toe app I found on Github by Group MINASTE. By the way, this app is also available on the App Store under the name Tic TAI Toe.

Clone or download the project (from Github) then build it in Xcode. Once the project has been built, we want to get it in .app.zip format.

Navigate to ~/Library/Developer/Xcode/DerivedData/ to find your app name, just check the starting part. For example, “MorpionTPE-dhzxccjhkowtjtfegrbiqgmnxewt”. Under that folder, go to Build/Products/{another folder named after your build target} to find the app file. Right-click and “Compress” to zip it, then rename it to MorpionTPE.app.zip.

Now that we have the app file and Appium set up, we can start playing the game with Appium!

Get Started

Create your test project, it can be done in your favourite editor and run on the command line. I used TestWise, a testing IDE as it’s very helpful to set up the initial Appium options.

In TestWise, you can select an Appium project and specify the app path:

Create a new Appium Test Automation Project in TestWise

TestWise will create a project skeleton with the Appium capability options to start up the TicTacToe app.

# test_helper.rb
def app_file_path
'/Users/courtney/sample-apps/MorpionTPE.app.zip'
end

def appium_xcuitest_opts
opts = {
caps: {
platformName: 'ios',
automationName: 'xcuitest',
platformVersion: '16.2', # change to match your Xcode
deviceName: 'iPhone 14', # change here if necessary
app: app_file_path
},
appium_lib: {
server_url: "http://127.0.0.1:4723",
wait: 0.1,
},
}
end

def appium_opts
return appium_xcuitest_opts()
end

Then, at the start of every file, it starts the driver with the above settings:

before(:all) do
@driver = Appium::Driver.new(appium_opts).start_driver
end

Now that our test’s skeleton is ready, we can start driving the Tic Tac Toe app.

Right-click the sample test case (new_spec.rb) and select Run "..." , you will see the app launching in an iOS simulator.

How to Play Tic Tac Toe

Given a 3x3 board, two players take turns selecting a cell. First to 3-in-a-row wins. The app let’s you pick between three game modes:

Tic TAI Toe’s game modes: Player VS Player, Player VS Computer and Computer VS Computer

In this exercise, I’ll select Player vs Player (PVP) mode, i.e. we can control the outcome (via scripts). Let’s play a very simple game so that Player 1 (X) wins.

The game plan:

# Begin game
Player 1: (1, 1)
Player 2: (3, 1)
Player 1: (1, 2)
Player 2: (3, 2)
Player 1: (1, 3) # Game ends, P1 wins

Execution Video

How to Play Tic Tac Toe with Appium

The board is made up of 9 cells (3x3). To play, we need to find and tap a specific cell.

Each cell does not have a unique identifier. However, all cells have the type XCUIElementTypeImage(getting this info via the Appium Inspector). I used driver.find_elements on the type to get all 9 cells. Then indexing to get a specific cell.

board = driver.find_elements(:class_name, "XCUIElementTypeImage")

# Board layout
# 0 1 2
# 3 4 5
# 6 7 8
board[0].click # taps the top-left cell

Using this, it’s simple to write a test case.

it "Play Tic Tac Toe - Player x Player - X Wins" do
driver.find_element(:name, "Player VS Player").click
board = driver.find_elements(:class_name, "XCUIElementTypeImage")

# 0 1 2
# 3 4 5
# 6 7 8
board[0].click
board[6].click
board[1].click
board[7].click
board[2].click

game_status_text = driver.find_element(:class_name, "XCUIElementTypeStaticText")["value"]
expect(game_status_text).to eq("Game has ended! Winner: Player X")
end

Using indexing is not very intuitive though, we can do better.

Refactoring

To make the script more readable, let’s use coordinates. While we are at it, we can refactor based on Maintainable Automated Test Design and use the Page Object Models design here as well.

First, I select driver.find_element(:name, "Player VS Player").click, then invoke the ‘Extract to Page Function’ refactoring in TestWise.

Then enter page and function name.

Apply the refactoring to get:

    menu_page = MenuPage.new(driver)
menu_page.click_player_vs_player_mode

Then, convert coordinates to indexes with a simple calculation.

class GameBoardPage < AbstractPage
def initialize(driver)
super(driver, "")
end

# (1, 1) is top-left, (3, 3) is bottom-right
def tap(x, y)
board = driver.find_elements(:class_name, "XCUIElementTypeImage")
index = (x - 1) * 3 + (y - 1)
board[index].click
end
end

The refactored test looks like:

it "Play Tic Tac Toe - Player x Player - X Wins" do
menu_page = MenuPage.new(driver)
menu_page.click_player_vs_player_mode

game_board_page = GameBoardPage.new(driver)
game_board_page.tap(1, 1)
game_board_page.tap(3, 1)
game_board_page.tap(1, 2)
game_board_page.tap(3, 2)
game_board_page.tap(1, 3)

expect(game_board_page.game_status_text).to eq("Game has ended! Winner: Player X")
end

Doesn’t it look much nicer?

Complete Test Scripts

menu_page.rb

class MenuPage < AbstractPage
def initialize(driver)
super(driver, "") # <= WIN TITLE
end

def click_player_vs_player_mode
driver.find_element(:name, "Player VS Player").click
end
end

game_board_page.rb

class GameBoardPage < AbstractPage
def initialize(driver)
super(driver, "")
end

def tap(x, y)
board = driver.find_elements(:class_name, "XCUIElementTypeImage")
index = (x - 1) * 3 + (y - 1)
board[index].click
end

def game_status_text
driver.find_element(:class_name, "XCUIElementTypeStaticText")["value"]
end
end

player_vs_player_spec.rb

load File.dirname(__FILE__) + "/../test_helper.rb"

describe "Player Vs Player Games" do
include TestHelper

before(:all) do
@driver = Appium::Driver.new(appium_opts).start_driver
end

after(:all) do
driver.quit unless debugging?
end

it "Play Tic Tac Toe - Player x Player - X Wins" do
menu_page = MenuPage.new(driver)
menu_page.click_player_vs_player_mode

game_board_page = GameBoardPage.new(driver)
game_board_page.tap(1, 1)
game_board_page.tap(3, 1)
game_board_page.tap(1, 2)
game_board_page.tap(3, 2)
game_board_page.tap(1, 3)

expect(game_board_page.game_status_text).to eq("Game has ended! Winner: Player X")
end

end

--

--