Most professional software comes with translations for user-visible texts such that users can continue using a familiar language. This poses an additional challenge for automated UI tests since user-visible texts can no longer be used to identify objects in case the test suite is to be executed with different translations for the user interfaces.
Translations In The Wild¶
In practice, it's not uncommon to see a huge increase of size of an object map when dealing with translated user interfaces. For instance, here's a small snippet taken from the object map used for testing the 'Qt' addressbook example shipped with Squish for Qt (we're using the Python scripting language here):
address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": "Address Book - Add"}
address_Book_Add_Forename_QLabel = {"text": "Forename:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": "Surname:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": "Phone:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": "Cancel", "type": "QPushButton", "window": address_Book_Add_Dialog}
This defines a name for a dialog used for adding new entries to the Addressbook.
If the test suite is now extended with new tests automating the same dialog, but in a different language (say: German), it's not unlikely to end up with an object map like this:
address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": "Address Book - Add"}
address_Book_Add_Forename_QLabel = {"text": "Forename:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": "Surname:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": "Phone:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": "Cancel", "type": "QPushButton", "window": address_Book_Add_Dialog}
adressbuch_Hinzufügen_Dialog = {"type": "Dialog", "windowTitle": "Adressbuch - Hinzufügen"}
adressbuch_Hinzufügen_Vorname_QLabel = {"text": "Vorname:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Nachname_QLabel = {"text": "Nachname:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Telefon_QLabel = {"text": "Telefon:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Abbrechen_QPushButton = {"text": "Abbrechen", "type": "QPushButton", "window": adressbuch_Hinzufügen_Dialog}
Note how the object map doubled in size. Furthermore, it not only became larger but also introduced a significant amount of duplication; changing the structure of the dialog used for adding entries (e.g. by replacing QLabel
controls with different kinds of controls) will require updating multiple object names.
Making Object Names Language Agnostic¶
In some cases, this issue can be addressed by reconfiguring Squish to not use properties which hold user-visible text when generating object names (see the chapter
Object Name Generation
in the Squish manual for more information). Instead, internal properties like id
or the like might be available which can be used instead. However, this has a number of downsides:
not all objects tend to have such stable internal identifiers so it's not uncommon that testers have to wait for engineers to add IDs for any relevant control
even if IDs are available, determining the ID for a given control on the screen can be tricky
the IDs tend to be a bit cryptic which negatively impacts readability of the test scripts; it's harder to figure out which controls a given test script interacts with by reading the test code
Scripting To The Rescue¶
In case internal identifiers are not available, a better approach might be to factor out user-visible texts into a separate module (a 'dictionary') in which each English text is mapped to a translation. This translation can be performed by a function which we will call tr
(short of translate) here.
The first step is to augment the original object map such that the tr
function is defined and used for all user visible texts:
def tr(text):
return text
address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": tr("Address Book - Add")}
address_Book_Add_Forename_QLabel = {"text": tr("Forename:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": tr("Surname:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": tr("Phone:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": tr("OK"), "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": tr("Cancel"), "type": "QPushButton", "window": address_Book_Add_Dialog}
Notice how the tr
function takes one argument and merely returns it: it has no effect whatsoever. It's used to mark all user-visible texts though. We can now move this function to a separate file (say english.py
) and import it from there:
def tr(text):
return text
from english import tr
address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": tr("Address Book - Add")}
address_Book_Add_Forename_QLabel = {"text": tr("Forename:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": tr("Surname:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": tr("Phone:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": tr("OK"), "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": tr("Cancel"), "type": "QPushButton", "window": address_Book_Add_Dialog}
The benefit is that we can now provide alternative definitions of the tr
function for other languages. For instance, the German translation might be:
def tr(text):
return {
"Address Book - Add": "Adressbuch - Hinzufügen",
"Forename:": "Vorname:",
"Surname:": "Nachname:",
"Phone:": "Telefon:",
"OK": "OK",
"Cancel": "Abbrechen",
}[text]
By simply using from german import tr
in our object map, all object names will then use the tr
function suitable for translating English texts to German and thus the tests can be used when using the addressbook example program with German translations.