TPS is an end to end test for Sync. It's name stands for Testing and Profiling tool for Sync (which is a misnomer, since it doesn't do any profiling), and it should not be confused with the similarly named tests in Talos.
TPS consists of a Firefox extension of the same name, along with a python test runner, both of which live inside mozilla-central. The python test runner will read a test file (in JavaScript format), setup one or more Firefox profiles with the necessary extensions and preferences, then launch Firefox and pass the test file to the extension. The extension will read the test file and perform a series of actions specified therein, such as populating a set of bookmarks, syncing to the Sync server, making bookmark modifications, etc.
A test file may contain an arbitrary number of sections, each involving the same or different profiles, so that one test file may be used to test the effect of syncing and modifying a common set of data (from a single Sync account) over a series of different events and clients.
To run TPS, you should create a new firefox account using a restmail.net email address (Strictly speaking, restmail isn't required, but it will allow TPS to automatically do account confirmation steps for you. Even if you opt not to use restmail, do not use your personal firefox account, as TPS will delete and replace the data in it many times, not to mention the first run is very likely to fail, since it expects a clean start).
Note: Be prepared not to use your computer for 15 or so minutes after starting a full run of TPS, as it will open and close a fairly large number of Firefox windows.
Clone mozilla-central (choose your flavor):
hg clone hg.mozilla.org/mozilla-centralor
git clone github.com/mozilla/gecko-dev
cd testing/tps
I suggest the path to be outside of the mc source tree
python create_venv.py --username=%EMAIL% --password=%PASSWORD% %PATH%
NOTE: If you are updating the TPS environment and want to keep your existing config (eg, the existing username and password), you should instead execute:
python create_venv.py --keep-config %PATH%
source %PATH%/bin/activate
Note that the testfile is NOT a path, it should only be the filename from services/sync/tests/tps/
runtps --debug --testfile %TEST_FILE_NAME% --binary %FIREFOX_BINARY_PATH%
--testfile
parameter will cause it to run all TPS tests listed in services/sync/tests/tps/all_tests.json
MOZ_HEADLESS=1
to run in headless mode (recommended)test_sync.js
testfile against a locally built firefox (where the mozconfig set the objdir to obj-ff-artifact
):MOZ_HEADLESS=1 runtps --debug --testfile test_sync.js --binary obj-ff-artifact/dist/Nightly.app/Contents/MacOS/firefox
$TPS_VENV_PATH/config.json
file. In particular, it will set preferences from the "preferences"
property, and so you can set the "identity.fxaccounts.autoconfig.uri"
preference to point to any FxA server you want. For example, a (partial) tps config for testing against stage might look like:{ // ... "fx_account": { "username": "foobar@restmail.net", "password": "hunter2" }, "preferences": { // use "https://stable.dev.lcip.org" for dev instead of stage "identity.fxaccounts.autoconfig.uri": "https://accounts.stage.mozaws.net" // possibly more preferences... }, // ... }
foobar@restmail.net
account must be registered on stage, otherwise authentication will fail (and the whole test will fail as well. You can sign up for an FxA account on stage or dev by creating an FxA account after adding the identity.fxaccounts.autoconfig.uri
preference (with the appropriate value) to about:config
. Additionally, note that the config file must parse as valid JSON, and so you can't have comments in it (sorry, I know this is annoying). One alternative is to put underscores before the "disabled" preferences, e.g. "_identity.fxaccounts.autoconfig.uri": "..."
.Each TPS test is run as a series of "phases". A phase runs in some firefox profile, and contains some set of actions to perform or check on that profile. Phases have an N to M relationship with profiles, where N >= M (there can never be more phases than profiles). Typically there are two profiles used, but any number of profiles could be used in theory (other than 0).
After the phases run, two additional "cleanup" phases are run, to unregister the devices with FxA. This is an implementation detail, but if you work with TPS for any amount of time you will almost certainly see cleanup-profile1
or similar in the logs. That's what that phase is doing, it does any necessary cleanup for the phase, primarially unregistering the device associated with that profile.
TPS tests tend to be broken down into three sections, in the following order (we'll cover these out of order, for the sake of simplicity)
It's worth noting that some parts of TPS assume that it can read the number off the end of the phase or profile to get to the next one, so try to stick to the convention established in the other tests. Yes, this is cludgey, but it's effective enough and nobody has changed it.
These map the phases to profiles. Both python and JavaScript read them in. They must look like:
var phases = { "phase1": "profile1", "phase2": "profile2", "phase3": "profile1" };
Between {
and }
it must be strict JSON. e.g. quoted keys, no trailing parentheses, etc. The python testrunner will be parsing it with an unforgiving call to json.loads
, so anything other than strict JSON will fail.
You can use as many profiles or phases as you need, but every phase you define later must be declared here, or it will not be run by the python test runner. Any phases declared here but not implemented later will cause the test to fail when it hits that phase.
A test file will contain one or more asset lists, which are lists of bookmarks, passwords, or other types of browser data that are relevant to Sync. The format of these asset lists vary somwhat depending on asset type.
The phase blocks are where the action happens! They tell TPS what to do. Each phase block contains the name of a phase, and a list of actions. TPS iterates through the phase blocks in alphanumeric order, and for each phase, it does the following:
phases
object that corresponds to this test phase.A phase is defined by calling the Phase
function with the name of the phase and a list of actions to perform:
Phase('phase1', [ [Bookmarks.add, bookmarks_initial], [Passwords.add, passwords_initial], [History.add, history_initial], [Sync, SYNC_WIPE_SERVER], ]);
Each action is an array, the first member of which is a function reference to call, the other members of which are parameters to pass to the function. Each type of asset list has a number of built-in functions you can call, described in the section on Asset lists; there are also some additional built-in functions.
Sync(options)
Initiates a Sync operation. If no options are passed, a default sync operation is performed. Otherwise, a special sync can be performed if one of the following are passed: SYNC_WIPE_SERVER
, SYNC_WIPE_CLIENT
, SYNC_RESET_CLIENT
. This will cause TPS to set the firstSync pref to the relevant value before syncing, so that the described actionwill take place
Logger.logInfo(msg)
Logs the given message to the TPS log.
Logger.AssertTrue(condition, msg)
Asserts that condition is true, otherwise an exception is thrown and the test fails.
Logger.AssertEqual(val1, val2, msg)
Asserts that val1 is equal to val2, otherwise an exception is thrown and the test fails.
You can also write your own functions to be called as actions. For example, consider the first action in the phase above:
[Bookmarks.add, bookmarks_initial]
You could rewrite this as a custom function so as to add some custom logging:
[async () => { Logger.logInfo("adding bookmarks_initial"); await Bookmarks.add(bookmarks_initial); }]
Note that this is probably best used for debugging, and new tests that want custom behavior should add it to the TPS addon so that other tests can use it.
Here's an example TPS test to tie it all together.
// Phase declarations var phases = { "phase1": "profile1", "phase2": "profile2", "phase3": "profile1" }; // Asset list // the initial list of bookmarks to be added to the browser var bookmarks_initial = { "menu": [ { uri: "http://www.google.com", title "google.com", changes: { // These properties are ignored by calls other than Bookmarks.modify title: "Google" } }, { folder: "foldera" }, { folder: "folderb" } ], "menu/foldera": [ { uri: "http://www.yahoo.com", title: "testing Yahoo", changes: { location: "menu/folderb" } } ] }; // The state of bookmarks after the first 'modify' action has been performed // on them. Note that it's equivalent to what you get after applying the properties // from "changes" var bookmarks_after_first_modify = { "menu": [ { uri: "http://www.google.com", title "Google" }, { folder: "foldera" }, { folder: "folderb" } ], "menu/folderb": [ { uri: "http://www.yahoo.com", title: "testing Yahoo" } ] }; // Phase implementation Phase('phase1', [ [Bookmarks.add, bookmarks_initial], [Sync, SYNC_WIPE_SERVER] ]); Phase('phase2', [ [Sync], [Bookmarks.verify, bookmarks_initial], [Bookmarks.modify, bookmarks_initial], [Bookmarks.verify, bookmarks_after_first_modify], [Sync] ]); Phase('phase3', [ [Sync], [Bookmarks.verify, bookmarks_after_first_modify] ]);
The effects of this test file will be:
bookmarks_initial
array, then they are synced to the Sync server. The SYNC_WIPE_SERVER
argument causes TPS to set the firstSync="wipeServer"
pref before syncing, in case the Sync account already contains data (this is typically unnecessary, and done largely as an example). Firefox closes.bookmarks_initial
list are present. Then it modifies those bookmarks by applying the "changes" property to each of them. E.g., the title of the first bookmark is changed from "google.com" to "Google". Next, the changes are synced to the Sync server. Finally, Firefox closes.bookmarks_after_first_modify
list are present; i.e., all the changes performed in profile2 have successfully been synced to profile1. Lastly, Firefox closes and the tests ends.runtps
with --debug
. This will enable much more verbose logging in all engines.tps.log
file written out after TPS runs. It will include log output written by the python driver, which includes information about where the temporary profiles it uses are stored.#sync
IRC channel!