diff --git a/pyxform/survey.py b/pyxform/survey.py
index 0ff0dd04b..1c902cd4e 100644
--- a/pyxform/survey.py
+++ b/pyxform/survey.py
@@ -713,8 +713,20 @@ def date_stamp(self):
"""Returns a date string with the format of %Y_%m_%d."""
return self._created.strftime("%Y_%m_%d")
- def _to_ugly_xml(self):
- return '' + self.xml().toxml()
+ def _to_testable_xml(self):
+ """Preserves attribute ordering across all Python versions.
+
+ See python_test_case.reorder_attributes for related code.
+ """
+ tree = ETree.fromstring(self.xml().toxml())
+ for el in tree.iter():
+ attrib = el.attrib
+ if len(attrib) > 1:
+ # adjust attribute order, e.g. by sorting
+ attribs = sorted(attrib.items())
+ attrib.clear()
+ attrib.update(attribs)
+ return ETree.tostring(tree, encoding="utf-8").decode("utf-8")
def _to_pretty_xml(self):
"""
diff --git a/pyxform/tests_v1/pyxform_test_case.py b/pyxform/tests_v1/pyxform_test_case.py
index 024ec93cd..112a34e4d 100644
--- a/pyxform/tests_v1/pyxform_test_case.py
+++ b/pyxform/tests_v1/pyxform_test_case.py
@@ -343,6 +343,8 @@ def reorder_attributes(root):
See bottom of https://docs.python.org/3/library/xml.etree.elementtree.html#element-objects and
https://github.com/python/cpython/commit/a3697db0102b9b6747fe36009e42f9b08f0c1ea8 for more information.
+
+ See survey._to_testable_xml for related code.
"""
for el in root.iter():
attrib = el.attrib
diff --git a/pyxform/tests_v1/test_dynamic_default.py b/pyxform/tests_v1/test_dynamic_default.py
index 0c105b0ce..3769eff8a 100644
--- a/pyxform/tests_v1/test_dynamic_default.py
+++ b/pyxform/tests_v1/test_dynamic_default.py
@@ -40,7 +40,7 @@ def test_handling_dynamic_default(self):
kwargs={"id_string": "id", "name": "dynamic", "title": "some-title"},
autoname=False,
)
- survey_xml = survey._to_pretty_xml()
+ survey_xml = survey._to_testable_xml()
self.assertContains(survey_xml, "", 1)
self.assertContains(survey_xml, "not_func$", 1)
@@ -92,7 +92,7 @@ def test_handling_dynamic_default_in_repeat(self):
kwargs={"id_string": "id", "name": "dynamic", "title": "some-title"},
autoname=False,
)
- survey_xml = survey._to_pretty_xml()
+ survey_xml = survey._to_testable_xml()
self.assertContains(survey_xml, "not_func$", 2)
self.assertContains(survey_xml, "", 2)
diff --git a/pyxform/tests_v1/test_whitespace.py b/pyxform/tests_v1/test_whitespace.py
index 1e93b51ff..dddcb07d3 100644
--- a/pyxform/tests_v1/test_whitespace.py
+++ b/pyxform/tests_v1/test_whitespace.py
@@ -30,6 +30,6 @@ def test_values_without_whitespaces_are_processed_successfully(self):
survey = self.md_to_pyxform_survey(md_raw=md)
expected = """"""
- xml = survey._to_pretty_xml()
+ xml = survey._to_testable_xml()
self.assertEqual(1, xml.count(expected))
self.assertPyxformXform(md=md, xml__contains=expected, run_odk_validate=True)