Appearance
Appearance
You probably do not want to spend a single second manually testing the LiveOps Dashboard for new releases (we're game developers, after all!) but, unfortunately, it can sometimes silently fail as game features are changed in incompatible ways. This might prevent your customer support staff from resolving issues, so it's a critical path component to routinely test.
The dashboard is composed of many moving parts, from full page views to individual UI components and then right down to JavaScript utility functions and our own core Metaplay SDK systems. Over time these parts change due to refactoring, adding new functionality, applying bug fixes, or just applying the type of routine general maintenance that comes with modern web development.
Whenever changes are made to one part of the dashboard, we need to have a high level of confidence that there are no unexpected or unintended changes to the other parts. This is where tests come in - they help to give us that high level of confidence that all parts of the dashboard are functioning as expected.
INFO
Good to know: These tests are always run by the automated build systems, so if you forget to run them (it happens!) you're still covered.
We use two different types of testing in the dashboard: end-to-end (e2e) and unit tests.
Before we look at each test type in detail, here's an overview of what your dashboard directory structure looks like. The tests
folder inside the LiveOps Dashboard project folder contains everything relevant to testing:
Dashboard LiveOps Dashboard project folder
+---src LiveOps Dashboard source files for compiling
+---public LiveOps Dashboard static web assets
+---gamespecific_tests Your tests live here
¦ +---e2e End-to-end tests
¦ +---unit Unit tests
+---cypress Cypress creates this when it runs
¦ +---screenshots Screenshots from tests
¦ +---videos Videos from tests
In e2e tests, a simulated user drives a browser window by clicking on buttons and entering text. These inputs are sent to the game server where they are processed as normal and the results are returned to the dashboard. The dashboard updates to reflect the new state and the tests then read back these results from the page to check that they match expectations. These tests check the whole flow of data from the browser to the game server and back again. They are a class of tests that is usually referred to as "end to end" tests. We use a tool called Cypress to do this kind of testing.
You can run Cypress from the command line in headless mode:
/Backend/Dashboard
in your favorite terminal emulator.pnpm i
to make sure all the dependencies are installed.pnpm backend
to start the local server (any other method of running it is also fine.)pnpm dev
to start a webserver to serve the dashboard on your localhost.pnpm test-e2e
to run all tests once.Cypress will use the Electron browser in headless mode to run the integration test specs, and you will get a report of the run as it completes.
As your game gets more complicated and the LiveOps Dashboard gets game-specific features, it might be worth the effort to add a few automated tests for them to save time on manual tests. It generally shouldn't take more than a few minutes to add a simple test and a few more to make sure it works as intended.
Instead of starting Cypress in headless mode, let's open it in the full visual mode to make test development easier:
/Backend/Dashboard
in your favorite terminal emulator.pnpm i
to make sure all the dependencies are installed.pnpm backend
to start the local server (any other method of running it is also fine.)pnpm dev
to start a webserver to serve the dashboard on your localhost.pnpm cypress open
to open the Cypress UI.Cypress will then open its own dashboard. From here you can run the pre-configured tests to see how Cypress works!
Metaplay's Idler example project has one game-specific test file: producers.cy.ts
. It is located in the gamespecific_tests/e2e
folder. You could edit this file, rename it freely, or create as many new ones as you like. All new files and folders under this root folder are automatically discovered by Cypress.
// producers.cy.ts
cy.maybeDescribe('Producers', [], function () { // Name of this test suite
before(function () { // Code that should run once before any of the individual tests
cy.visit(`/players/${this.testPlayer.id}`) // Calls a custom command that opens a dummy player's details page
})
it('Checks that the producers card renders', function () { // Name of this individual test
cy.get('[data-testid=player-producers-card]') // Check that the page contains an HTML element with the id "player-producers-card"
})
// Add as many more tests as you'd like here!
})
For the most basic tests of checking that an element exists, you can most likely just copy/paste from the existing test code. This is a great test because it is simple to write, robust against changes, and errors in Vue usually prevent elements from rendering, making this a great catch-all for unexpected problems. For more advanced tests, like filling in forms and using buttons, have a look at Cypress's great online documentation.
There are a couple of things to be aware of when writing Cypress tests for the Metaplay SDK:
cy.maybeDescribe
instead of Cypress’ built-in describe
function. Our maybeDescribe
function adds support for easily disabling tests during development. We’ll cover this in more detail below.function () {}
) instead of modern, arrow-style functions (() => {}
) when defining your tests and callbacks. This is actually a Cypress requirement - it affects the scope of this
and thus changes the visibility of Cypress features inside your tests. You’ll mostly be fine if you use arrow-style functions - nothing will break, but you might find that some Cypress features aren’t available. It’s good to be in the habit of not using arrow-style functions when writing your tests.TIP
Protip: Testing is much better against data that you can safely mutate. We use Cypress' support tools to first create a new player and then wrap it into an alias called testPlayer
. This is done before each set of tests run, so you can always trust that a fresh test player is available for your specs!
At this point, it helps to clarify some Cypress terms:
it
blocks.cy.maybeDescribe
blocks.cy.ts
extension, that contains test suites. You should follow the rule of thumb that one spec file should contain exactly one test suite. Cypress automatically discovers test suites (and therefore tests) that are inside these spec files.As mentioned above, you should use cy.maybeDescribe
instead of the Cypress standard describe
when declaring your test suites. The format of the function is as follows:
// sample.spec.ts
cy.maybeDescribe(name, features, function () {
// Test code goes here.
})
Where:
name
is the name of your test suite. This name appears in the Cypress dashboard and in the test results. The name is usually just a human-readable version of the filename.features
is a list of features that the test suite is related to as a list of strings. The test suite will be skipped if any of the named features are disabled in the server. In your own tests, you can just set this as the empty array []
.Tests are great when they pass, but they can be a pain when they fail - especially if an unrelated test fails when you’re working on something else. We all know that sometimes you just need to disable a test until you have time to fix it properly. Cypress provides the ability to easily .skip
individual tests or entire test suites. This can be a lifesaver in difficult situations, but it’s easy to forget to re-enable the tests when you commit your code. Tests that are disabled are tests that won’t help you, so you’ll want to be super careful about this.
INFO
Good to know: The Metaplay SDK also adds the ability to disable test suites based on features. We use this internally, for example, to prevent tests for guild functionality from being run if your game doesn’t have guilds enabled. The list of disabled features is auto-discovered when Cypress starts, so you don’t need to do anything with feature-based enabling of tests, but it’s good that you know it’s there.
Unit tests are the familiar "function level" tests that are discussed a lot in computer science. We run these against the code in our utility libraries. Our unit tests are written using Vitest.
It's easy to run the unit tests:
/Backend/Dashboard
in your favorite terminal emulator.pnpm i
to make sure all the dependencies are installed.pnpm test-unit
to run all of the unit tests.If add game-specific utility libraries to the dashboard, then you'll most likely also want to write some tests too. Learning how to write tests is beyond the scope of our documentation. However, because we use standard test tools and libraries, if you have any experience writing tests for Node-based applications then you'll find it easy to add your own. If you've based your project on one of our sample projects, then the test configuration is already included and ready to go. Or check out our Idler sample for some example tests to get you started.
When writing your own unit tests, you just need to put them in the right place. This means placing them in the Backend/Dashboard/gamespecific_tests/unit
directory and giving them the .spec.ts
extension.
In our sample projects, we include this trivial example test in Backend/Dashboard/gamespecific_tests/unit/sample.spec.ts
as a starting point for your own tests:
// sample.spec.ts
import { expect } from 'chai'
import 'mocha'
describe('trivial example', () => {
it('passes', () => {
expect(123).to.not.equal(456)
})
})
After you have your testing environment set up, you can start looking into web testing best practices and how to take full advantage of what Cypress offers. Check out the Cypress documentation for more information.