Repository: FontoXML/fontoxpath
Pull request #422 (2021-09-19 09:26:27)

Implement one-argument form of fn:serialize()

bundlemon[bot]: <!-- bundlemon -->
## BundleMon

  
  
  
  
  
<details open>
<summary>Files updated (2)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | fontoxpath.js<br/> | 95.36KB (+78B +0.08%) | -
:white_check_mark: | fontoxpath.esm.js<br/> | 95.49KB (+77B +0.08%) | -
</details>

  
  


Total files change +155B +0.08%

Final result: :white_check_mark:
rrthomas: Note: I do not even pretend this works yet!

I got stuck trying to make closure happy: as with xspatterns, I made slimdom a run-time dependency, but there seems to be a problem with type annotations; I get 4 warnings like the following:

```
ClosureCompiler: /home/rrt/.local/var/repo/fontoxpath/.tscc_temp/50a970eb1f9d4179737a70bb05d4ead23d4e1021b022b47f2580dce735744e9f/externs_generated.js:657:67: WARNING - [JSC_TYPE_PARSE_ERROR] Bad type annotation. expected closing } See https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler for more information.
  657|  * @implements {node_modules$slimdom$dist$slimdom$$d$$ts.ParentNode|node_modules$slimdom$dist$slimdom$$d$$ts.NonDocumentTypeChildNode|node_modules$slimdom$dist$slimdom$$d$$ts.ChildNode}
```

This seems to relate to: https://ask.clojure.org/index.php/6240/jsdoc-parsing-fails-recognize-union-types-breaking-resolution

As a result (I guess), when I try a simple test I get:

```
slimdom is not defined
```

Of course, I don't know whether my actual implementation is any good either! I hope that it's fairly simple, anyway.
DrRataplan: It seemed like such a great idea: an XPath engine without any hard dependency on any specific DOM implementation; as long as it implements the DOM interfaces, we accept it. This has not been making development easy, but I still do not want to give it up. Having an XPath implementation that can work with the native DOM (in the browser) as well as lightweight implementations (like slimdom) is very powerful.

This means we should either implement serialization in FontoXPath, or find a dependency that does the minimal part of it, or make it injectable: an extra option XMLSerializer that can be passed when calling `evaluateXPath`. The XMLSerializer packed with slimdom does not like nodes that it did not create itself, so that is a dead path. 

I think I feel most for an option `xmlSerializer`, that will be placed on the `evaluationParameters`, next to the `documentWriter` and `nodesFactory`.



rrthomas: Thanks for the design guidance, @DrRataplan, I'll have a go at implementing that. Also, thanks for the hint re slimdom; I guess I'll have to find another serializer for my application, but it seems there are some to choose from.
rrthomas: I've implemented this as you suggest, @DrRataplan, and a basic test with `xmlserializer` works (I note that `w3c-xmlserializer` might be a good option, but it doesn't implement `XMLSerializer` out of the box, so would need a wrapper.)

I have pushed that; I'll now see if I can wire up the fontoxpaths to also use `xmlserializer`.
rrthomas: Some of the tests now seem to be passing; unfortunately not most, as they require the two-argument form of `serialize()`. I haven't looked, but it may be that some of those would pass with a trivial implementation!
rrthomas: @DrRataplan Thanks very much for your help with this!
coveralls: 
[![Coverage Status](https://coveralls.io/builds/43211461/badge)](https://coveralls.io/builds/43211461)

Coverage increased (+0.004%) to 95.964% when pulling **11b86e283210e337659a0e3c08b9f07a90097f88 on rrthomas:add-fn-serialize** into **92dde2b11479ac75d29e9c323177857e1bcbb691 on FontoXML:master**.

DrRataplan: Hey there,

I just pushed some changes on top of your PR: I declared some better local typing, fixed some linting issues and I added some unit tests. Lets see how the CI likes the new coverage for tests. The error cases are often missed in the normal QT3 tests.

TODO: verify coverage, finish the public API part and consider to implement (a part of) the two-argument version.

Thanks again for this! This collaboration is very helpful!

Regards,

Martin


rrthomas: I just tried using this code with your changes, @DrRataplan, and I found a couple of things I don't understand:

1. When setting the `xmlSerializer` property, I have to write something like `xmlSerializer: new slimdom.XMLSerializer() as fontoxpath.XMLSerializer`. The tests use slimdom.XMLSerializer without this cast, so I'm not sure why I need it.
2. `serialize()` seems to return something that isn't a string. I can't work out what's going wrong from looking at the source code, but when I pass the result of `serialize()` to my own user function that declares its argument type to be `xs:string`, with a corresponding TypeScript signature of `string`, the actual argument is an `object`, and if I call `.toString()` I get the value I expect.

Sorry I don't have minimal failing examples yet, I'm just trying to get some code working, and working around as above is good enough for me for now. Feel free to ignore this report until I can supply a proper example!
rrthomas: I have refreshed this PR on top of current master. I am delighted to see that `distinct-values` is now implemented, which means that all of functx now compiles except for `functx:open-ref-document()`, which needs `base-uri()`.
DrRataplan: Hey Reuben,

This seems to have gotten a bit snowed over I'm afraid! The implementation looks AOK to me and I'm very happy we seem to be close to 'supporting' the functx library! To be honest I would've never thought we would ever get close!

That base-uri function does not seem too hard to implement at first sight, for the browser DOM that is. However I see in Ruth you are using slimdom, which does not support the baseURI property on Node. I really wonder how that is supposed to work though in for example NodeJS where your files might not even come from disk. Lets see, I'm always happy to implement `fn:base-uri` to returning `about:blank` or something for any DOM implementation that does not support base-uri, but that is very silly.

I'm integrating this now and I'm planning to make a new release this week. Thanks for the help!

Kind regards,

Martin

Patch:
diff --git a/src/evaluationUtils/buildEvaluationContext.ts b/src/evaluationUtils/buildEvaluationContext.ts
index 55621ecd..e89ea973 100644
--- a/src/evaluationUtils/buildEvaluationContext.ts
+++ b/src/evaluationUtils/buildEvaluationContext.ts
@@ -92,6 +92,7 @@ export default function buildEvaluationContext(
 			namespaceResolver: externalOptions['namespaceResolver'],
 			functionNameResolver: externalOptions['functionNameResolver'],
 			nodesFactory: externalOptions['nodesFactory'],
+			xmlSerializer: externalOptions['xmlSerializer'],
 		};
 	} else {
 		internalOptions = {
@@ -102,6 +103,7 @@ export default function buildEvaluationContext(
 			namespaceResolver: null,
 			nodesFactory: null,
 			documentWriter: null,
+			xmlSerializer: null,
 		};
 	}
 	const wrappedDomFacade: DomFacade = new DomFacade(
@@ -144,6 +146,8 @@ export default function buildEvaluationContext(
 		? wrapExternalDocumentWriter(internalOptions.documentWriter)
 		: domBackedDocumentWriter;
 
+	const xmlSerializer: XMLSerializer = internalOptions.xmlSerializer;
+
 	const variableBindings: { [s: string]: () => ISequence } = Object.keys(variables).reduce(
 		(typedVariableByName, variableName) => {
 			const variable = variables[variableName];
@@ -193,6 +197,7 @@ export default function buildEvaluationContext(
 		externalOptions['currentContext'],
 		new Map(),
 		internalOptions.logger,
+		xmlSerializer,
 		true
 	);
 
diff --git a/src/expressions/ExecutionParameters.ts b/src/expressions/ExecutionParameters.ts
index def4dc2f..ddba0a6d 100644
--- a/src/expressions/ExecutionParameters.ts
+++ b/src/expressions/ExecutionParameters.ts
@@ -14,6 +14,7 @@ export default class ExecutionParameters {
 		public readonly currentContext: any,
 		public readonly rootPointerByRootNodeMap: Map<ConcreteNode | TinyNode, NodePointer>,
 		public readonly logger?: Logger,
+		public readonly xmlSerializer?: XMLSerializer,
 		public readonly annotateAst: boolean = true
 	) {}
 }
diff --git a/src/expressions/functions/builtInFunctions_sequences.ts b/src/expressions/functions/builtInFunctions_sequences.ts
index 1465e501..21e63a4d 100644
--- a/src/expressions/functions/builtInFunctions_sequences.ts
+++ b/src/expressions/functions/builtInFunctions_sequences.ts
@@ -1,3 +1,5 @@
+import { NodePointer } from '../../domClone/Pointer';
+import realizeDom from '../../domClone/realizeDom';
 import atomize from '../dataTypes/atomize';
 import castToType from '../dataTypes/castToType';
 import createAtomicValue from '../dataTypes/createAtomicValue';
@@ -688,6 +690,37 @@ const fnFoldRight: FunctionDefinitionType = (
 	);
 };
 
+const fnSerialize: FunctionDefinitionType = (
+	_dynamicContext,
+	executionParameters,
+	_staticContext,
+	sequence
+) => {
+	if (!executionParameters.xmlSerializer) {
+		throw new Error('serialize() called but no xmlSerializer set in execution parameters.');
+	}
+
+	const allResults = sequence.getAllValues();
+
+	if (
+		!allResults.every((value) => {
+			return isSubtypeOf(value.type, ValueType.NODE);
+		})
+	) {
+		throw new Error('Expected argument to fn:serialize to resolve to a sequence of Nodes.');
+	}
+	return sequenceFactory.singleton(
+		createAtomicValue(
+			allResults.map((nodeValue) => {
+				return executionParameters.xmlSerializer.serializeToString(
+					realizeDom(nodeValue.value as NodePointer, executionParameters, false) as Node
+				);
+			}),
+			ValueType.XSSTRING
+		)
+	);
+};
+
 const declarations: BuiltinDeclarationType[] = [
 	{
 		argumentTypes: [{ type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE }],
@@ -1039,6 +1072,14 @@ const declarations: BuiltinDeclarationType[] = [
 		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
 		returnType: { type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE },
 	},
+
+	{
+		argumentTypes: [{ type: ValueType.ITEM, mult: SequenceMultiplicity.ZERO_OR_MORE }],
+		callFunction: fnSerialize,
+		localName: 'serialize',
+		namespaceURI: BUILT_IN_NAMESPACE_URIS.FUNCTIONS_NAMESPACE_URI,
+		returnType: { type: ValueType.XSSTRING, mult: SequenceMultiplicity.EXACTLY_ONE },
+	},
 ];
 
 export default {
diff --git a/src/index.ts b/src/index.ts
index c561ac6b..04627f8d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -59,6 +59,7 @@ import {
 	NamespaceResolver,
 	Options,
 	ResolvedQualifiedName,
+	XMLSerializer,
 } from './types/Options';
 import {
 	Attr,
@@ -257,6 +258,7 @@ export {
 	getBucketForSelector,
 	getBucketsForNode,
 	parseScript,
+	XMLSerializer,
 	precompileXPath,
 	profiler,
 	registerCustomXPathFunction,
diff --git a/src/types/Options.ts b/src/types/Options.ts
index c1c1d981..eb6e3bd2 100644
--- a/src/types/Options.ts
+++ b/src/types/Options.ts
@@ -1,6 +1,6 @@
 import IDocumentWriter from '../documentWriter/IDocumentWriter';
 import INodesFactory from '../nodesFactory/INodesFactory';
-
+import { Node } from './Types';
 /**
  * Specifies which language to use.
  *
@@ -56,6 +56,14 @@ export type FunctionNameResolver = (
  */
 export type NamespaceResolver = (prefix: string) => string | null;
 
+/**
+ * An XML serializer that can serialzie nodes. Used when the `fn:serialize` function is called with
+ * a node
+ *
+ * @public
+ */
+export type XMLSerializer = { serializeToString: (root: Node) => string };
+
 /**
  * @public
  */
@@ -163,4 +171,9 @@ export type Options = {
 	 * elements using the document implementation related to the passed context node.
 	 */
 	nodesFactory?: INodesFactory;
+
+	/**
+	 * Serializer used for fn:serialize().
+	 */
+	xmlSerializer?: XMLSerializer;
 };


Test patch:
diff --git a/test/assets/runnableTestSets.csv b/test/assets/runnableTestSets.csv
index 98e1a708..1045372f 100644
--- a/test/assets/runnableTestSets.csv
+++ b/test/assets/runnableTestSets.csv
@@ -158,7 +158,7 @@ fn-round-half-to-even,true
 fn-seconds-from-dateTime,true
 fn-seconds-from-duration,true
 fn-seconds-from-time,true
-fn-serialize,false
+fn-serialize,true
 fn-serialize-json,false
 fn-sort,false
 fn-starts-with,true
diff --git a/test/assets/unrunnableTestCases.csv b/test/assets/unrunnableTestCases.csv
index ead58ff6..169bba49 100644
--- a/test/assets/unrunnableTestCases.csv
+++ b/test/assets/unrunnableTestCases.csv
@@ -594,8 +594,6 @@ fn-function-lookup-393,AssertionError: Expected XPath exists(function-lookup(fn:
 fn-function-lookup-394,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-395,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'parse-xml-fragment'), 1)) to resolve to true: expected false to be true
 fn-function-lookup-396,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
-fn-function-lookup-397,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'serialize'), 1)) to resolve to true: expected false to be true
-fn-function-lookup-398,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-399,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'serialize'), 2)) to resolve to true: expected false to be true
 fn-function-lookup-400,Error: XPTY0004: Expected base expression of a function call to evaluate to a sequence of single function item
 fn-function-lookup-413,AssertionError: Expected XPath exists(function-lookup(fn:QName('http://www.w3.org/2005/xpath-functions', 'default-collation'), 0)) to resolve to true: expected false to be true
@@ -2942,8 +2940,6 @@ function-literal-393,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-fu
 function-literal-394,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}parse-xml with arity of 1 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}parse-json (xs:string)"?
 function-literal-395,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}parse-xml-fragment with arity of 1 not registered. No similar functions found.
 function-literal-396,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}parse-xml-fragment with arity of 1 not registered. No similar functions found.
-function-literal-397,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 1 not registered. No similar functions found.
-function-literal-398,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 1 not registered. No similar functions found.
 function-literal-399,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 2 not registered. No similar functions found.
 function-literal-400,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}serialize with arity of 2 not registered. No similar functions found.
 function-literal-413,Error: XPST0017: Function Q{http://www.w3.org/2005/xpath-functions}default-collation with arity of 0 not registered. No similar functions found.
@@ -3916,8 +3912,6 @@ fo-test-fn-element-with-id-001,Error: No selector counterpart for: validateExpr.
 fo-test-fn-element-with-id-002,Error: No selector counterpart for: validateExpr.
 fo-test-fn-idref-001,Error: No selector counterpart for: validateExpr.
 fo-test-fn-idref-002,Error: No selector counterpart for: validateExpr.
-fo-test-fn-serialize-001,Error: XPST0017: Function fn:serialize with arity of 2 not registered. No similar functions found.
-fo-test-fn-serialize-002,Error: XPST0017: Function fn:serialize with arity of 2 not registered. No similar functions found.
 fo-test-fn-filter-002,Error: XPST0017: Function fn:lang with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2001/XMLSchema}long (xs:anyAtomicType?)"?
 fo-test-fn-for-each-pair-001,Error: XPST0017: Function fn:for-each-pair with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}for-each-pair (array(*), array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/array}for-each (array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/map}for-each (map(*), item()*)" or "Q{http://www.w3.org/2005/xpath-functions}for-each (item()*, function(*))"?
 fo-test-fn-for-each-pair-002,Error: XPST0017: Function fn:for-each-pair with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}for-each-pair (array(*), array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/array}for-each (array(*), function(*))", "Q{http://www.w3.org/2005/xpath-functions/map}for-each (map(*), item()*)" or "Q{http://www.w3.org/2005/xpath-functions}for-each (item()*, function(*))"?
@@ -3946,4 +3940,121 @@ fo-test-array-subarray-002,Error: FOAY0001: subarray start out of bounds.
 fo-test-array-subarray-006,Error: FOAY0001: subarray start out of bounds.
 fo-test-array-subarray-007,Error: FOAY0001: subarray start out of bounds.
 fo-test-array-fold-right-003,AssertionError: Expected XPath          array:fold-right([1,2,3], [], function($x, $y){[$x, $y]})        to (deep equally) resolve to [1, [2, [3, []]]]: expected false to be true
-fo-test-array-sort-002,Error: XPST0017: Function array:sort with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}sort (array(*))", "Q{http://www.w3.org/2001/XMLSchema}short (xs:anyAtomicType?)" or "Q{http://www.w3.org/2005/xpath-functions/math}sqrt (xs:double?)"?
\ No newline at end of file
+fo-test-array-sort-002,Error: XPST0017: Function array:sort with arity of 3 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions/array}sort (array(*))", "Q{http://www.w3.org/2001/XMLSchema}short (xs:anyAtomicType?)" or "Q{http://www.w3.org/2005/xpath-functions/math}sqrt (xs:double?)"?
+serialize-xml-003,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-004,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-005,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-006,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-007,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-007a,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-008,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-009,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-012,AssertionError: expected [Function] to throw error matching /SENR0001/ but got 'XPST0003: Unable to parse: "serialize((//namespace::*)[1])".\nExpected [A-Z,_,a-z,À-Ö,Ø-ö,ø-˿,Ͱ-ͽ,Ϳ-῿,‌,‍,⁰-↏,Ⰰ-⿯,、-퟿,豈-﷏,ﷰ-�] or [\ud800-\udb7f] but ":" found.\nserialize((//namespace:[Error is around here]:*)[1])'
+serialize-xml-013,AssertionError: expected [Function] to throw error matching /SENR0001/ but got 'No selector counterpart for: computedNamespaceConstructor.'
+serialize-xml-014,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-015,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-016,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-017,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-018,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-019,AssertionError: expected [Function] to throw error matching /SEPM0019/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-020,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-021,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-022,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-023,AssertionError: expected [Function] to throw error matching /SEPM0017/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-024,AssertionError: expected [Function] to throw error matching /SEPM0018/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-025,AssertionError: expected [Function] to throw error matching /SEPM0019/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-026,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-027,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-028,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-029,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-030,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-031,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-032,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-033,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-034,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-101,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-102,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-103,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-104,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-105,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-106,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-106a,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-107,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-107a,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-108,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-109,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-110,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-119,AssertionError: expected [Function] to throw error matching /XQDY0137/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-120,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-123,AssertionError: expected [Function] to throw error matching /SEPM0016/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-124,AssertionError: expected [Function] to throw error matching /XQDY0137/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-125,AssertionError: expected [Function] to throw error matching /XQDY0137/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-126,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-127,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-128,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-129,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-130,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-131,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-131a,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-132,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-133,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-134,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-137,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-138,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-xml-139,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-140,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-141,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-xml-142,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-001,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-002,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-003,Error: XPST0003: Unable to parse: "(let $params := map {             "method" : "json"             },                $arg := [1, 2 , [3,4,5], 6]                     return serialize($arg, $params)         ) instance of xs:string". Expected " ", "!", "!=", "(", "(:", "*", "+", "-", "/", "//", "<", "<<", "<=", "=", "=>", ">", ">=", ">>", "?", "[", "\n", "\r", "\t", "]", "and", "cast", "castable", "div", "eq", "except", "ge", "gt", "idiv", "instance", "intersect", "is", "le", "lt", "mod", "ne", "or", "to", "treat", "union", "|", or "||" but "," found. (let $params := map {             "method" : "json"             },                $arg := [1, 2 [Error is around here], [3,4,5], 6]                     return serialize($arg, $params)         ) instance of xs:string
+serialize-json-004,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-005,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-006,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-007,AssertionError: expected [Function] to throw error matching /SERE0023/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-008,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-009,Error: No selector counterpart for: computedDocumentConstructor.
+serialize-json-010,AssertionError: expected [Function] to throw error matching /SERE0022/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-011,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-101,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-102,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-103,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-104,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-105,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-106,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-107,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-108,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-109,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-110,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-111,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-112,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-113,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-114,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-115,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-116,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-117,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-118,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-119,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-120,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-121,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-122,AssertionError: expected [Function] to throw error matching /SERE0020/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-123,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-124,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-125,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-126,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-127,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-128,Error: XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+serialize-json-130,AssertionError: expected [Function] to throw error matching /SERE0023/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-131,AssertionError: expected [Function] to throw error matching /SERE0023/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-132,AssertionError: expected [Function] to throw error matching /SERE0023/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-133,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-134,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+serialize-json-135,AssertionError: expected [Function] to throw error matching /XPTY0004/ but got 'XPST0017: Function serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?'
+fo-test-fn-serialize-001,Error: XPST0017: Function fn:serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+fo-test-fn-serialize-002,Error: XPST0017: Function fn:serialize with arity of 2 not registered. Did you mean "Q{http://www.w3.org/2005/xpath-functions}serialize (item()*)"?
+fn-function-lookup-398,Error: serialize() called but no xmlSerializer set in execution parameters.
+serialize-xml-001,Error: serialize() called but no xmlSerializer set in execution parameters.
+serialize-xml-002,AssertionError: expected [Function] to throw error matching /SENR0001/ but got 'serialize() called but no xmlSerializer set in execution parameters.'
+serialize-xml-010,AssertionError: expected [Function] to throw error matching /SENR0001/ but got 'serialize() called but no xmlSerializer set in execution parameters.'
+serialize-xml-011,AssertionError: expected [Function] to throw error matching /SENR0001/ but got 'serialize() called but no xmlSerializer set in execution parameters.'
+function-literal-398,Error: serialize() called but no xmlSerializer set in execution parameters.
\ No newline at end of file
diff --git a/test/qt3tests.ts b/test/qt3tests.ts
index e5ad4c35..7a3c2bd9 100644
--- a/test/qt3tests.ts
+++ b/test/qt3tests.ts
@@ -16,7 +16,7 @@ import {
 	ReturnType,
 } from 'fontoxpath';
 import { acceptAst, IAstRejected, rejectAst } from 'fontoxpath/jsCodegen/JavaScriptCompiledXPath';
-import { Element, Node, XMLSerializer } from 'slimdom';
+import { Element, Node, XMLSerializer as SlimdomXMLSerializer } from 'slimdom';
 import { slimdom } from 'slimdom-sax-parser';
 import {
 	ALL_TESTS_QUERY,
@@ -50,7 +50,20 @@ function createNodesFactory(assertNode: Node) {
 	return nodesFactory;
 }
 
-function createAsserterForExpression(baseUrl: string, assertNode, language, annotateAst: boolean) {
+type AsserterForExpression = (
+	xpath: string,
+	contextNode: Node,
+	variablesInScope: { [s: string]: unknown },
+	namespaceResolver: (prefix: string) => string,
+) => void;
+
+function createAsserterForExpression(
+	baseUrl: string,
+	assertNode,
+	language,
+	annotateAst: boolean,
+	xmlSerializer: SlimdomXMLSerializer
+): AsserterForExpression {
 	const nodesFactory = createNodesFactory(assertNode);
 
 	switch (assertNode.localName) {
@@ -58,7 +71,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 			const asserts = evaluateXPathToNodes('*', assertNode, undefined, {
 				annotateAst: false,
 			}).map((innerAssertNode) =>
-				createAsserterForExpression(baseUrl, innerAssertNode, language, annotateAst)
+				createAsserterForExpression(
+					baseUrl,
+					innerAssertNode,
+					language,
+					annotateAst,
+					xmlSerializer
+				)
 			);
 			return (xpath: string, contextNode, variablesInScope, namespaceResolver) =>
 				asserts.forEach((a) => a(xpath, contextNode, variablesInScope, namespaceResolver));
@@ -67,7 +86,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 			const asserts = evaluateXPathToNodes('*', assertNode, undefined, {
 				annotateAst: false,
 			}).map((innerAssertNode) =>
-				createAsserterForExpression(baseUrl, innerAssertNode, language, annotateAst)
+				createAsserterForExpression(
+					baseUrl,
+					innerAssertNode,
+					language,
+					annotateAst,
+					xmlSerializer
+				)
 			);
 			return (xpath, contextNode, variablesInScope, namespaceResolver) => {
 				const errors = [];
@@ -108,6 +133,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 							nodesFactory,
 							language,
 							annotateAst,
+							xmlSerializer,
 						});
 					},
 					errorCode === '*' ? /.*/ : new RegExp(errorCode),
@@ -150,7 +176,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst: false }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst: false,
+							xmlSerializer,
+						}
 					),
 					xpath
 				);
@@ -178,6 +210,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						nodesFactory,
 						language,
 						annotateAst,
+						xmlSerializer,
 					}),
 					`Expected XPath ${xpath} to resolve to true`
 				);
@@ -213,7 +246,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst,
+							xmlSerializer,
+						}
 					),
 					`Expected XPath ${xpath} to resolve to ${equalWith}`
 				);
@@ -245,7 +284,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst,
+							xmlSerializer,
+						}
 					),
 					`Expected XPath ${xpath} to (deep equally) resolve to ${equalWith}`
 				);
@@ -274,7 +319,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst,
+							xmlSerializer,
+						}
 					),
 					`Expected XPath ${xpath} to resolve to the empty sequence`
 				);
@@ -302,6 +353,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						nodesFactory,
 						language,
 						annotateAst,
+						xmlSerializer,
 					}),
 					`Expected XPath ${xpath} to resolve to false`
 				);
@@ -337,7 +389,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst,
+							xmlSerializer,
+						}
 					),
 					expectedCount,
 					`Expected ${xpath} to resolve to ${expectedCount}`
@@ -371,7 +429,13 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						contextNode,
 						null,
 						variablesInScope,
-						{ namespaceResolver, nodesFactory, language, annotateAst }
+						{
+							namespaceResolver,
+							nodesFactory,
+							language,
+							annotateAst,
+							xmlSerializer,
+						}
 					),
 					`Expected XPath ${xpath} to resolve to something of type ${expectedType}`
 				);
@@ -397,18 +461,21 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 			if (
 				evaluateXPathToBoolean('@file', assertNode, undefined, {
 					annotateAst,
+					xmlSerializer,
 				})
 			) {
 				parsedFragment = getFile(
 					evaluateXPathToString('$baseUrl || "/" || @file', assertNode, null, {
 						baseUrl,
 						annotateAst,
+						xmlSerializer,
 					})
 				);
 			} else {
 				parsedFragment = parser.parseFromString(
 					`<xml>${evaluateXPathToString('.', assertNode, undefined, {
 						annotateAst,
+						xmlSerializer,
 					})}</xml>`
 				).documentElement;
 			}
@@ -418,6 +485,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 					nodesFactory,
 					language,
 					annotateAst,
+					xmlSerializer,
 				}) as Node[];
 
 				chai.assert(
@@ -431,10 +499,11 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						},
 						{
 							annotateAst,
+							xmlSerializer,
 						}
 					),
 					`Expected XPath ${xpath} to resolve to the given XML. Expected ${results
-						.map((result) => new XMLSerializer().serializeToString(result))
+						.map((result) => new SlimdomXMLSerializer().serializeToString(result))
 						.join(' ')} to equal ${
 						parsedFragment.nodeType === parsedFragment.DOCUMENT_FRAGMENT_NODE
 							? parsedFragment.childNodes
@@ -475,7 +544,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						}
 					),
 					`Expected preparsed XPath ${xpath} to resolve to the given XML. Expected ${resultsWithPreparsedQuery
-						.map((result) => new XMLSerializer().serializeToString(result))
+						.map((result) => new SlimdomXMLSerializer().serializeToString(result))
 						.join(' ')} to equal ${
 						parsedFragment.nodeType === parsedFragment.DOCUMENT_FRAGMENT_NODE
 							? parsedFragment.childNodes
@@ -489,6 +558,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 		case 'assert-string-value': {
 			const expectedString = evaluateXPathToString('.', assertNode, undefined, {
 				annotateAst: false,
+				xmlSerializer,
 			});
 			return (xpath, contextNode, variablesInScope, namespaceResolver) => {
 				chai.assert.equal(
@@ -497,6 +567,7 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
 						nodesFactory,
 						language,
 						annotateAst,
+						xmlSerializer,
 					}),
 					expectedString,
 					xpath
@@ -518,20 +589,20 @@ function createAsserterForExpression(baseUrl: string, assertNode, language, anno
  */
 function createAsserterForJsCodegen(
 	baseUrl: string,
-	assertNode,
-	language
+	assertNode: Element,
+	language: Language
 ): (
-	xpath,
-	contextNode,
-	variablesInScope,
-	namespaceResolver,
-	that
+	xpath: string,
+	contextNode: Node,
+	variablesInScope: { [s: string]: unknown },
+	namespaceResolver: (prefix: string) => string,
+	that: Mocha.TestFunction
 ) => JavaScriptCompiledXPathResult {
 	const nodesFactory = createNodesFactory(assertNode);
 
 	switch (assertNode.localName) {
 		case 'all-of': {
-			const asserts = evaluateXPathToNodes('*', assertNode, undefined, {
+			const asserts = evaluateXPathToNodes<Element>('*', assertNode, undefined, {
 				annotateAst: false,
 				language,
 			}).map((innerAssertNode) =>
@@ -566,7 +637,7 @@ function createAsserterForJsCodegen(
 			};
 		}
 		case 'any-of': {
-			const asserts = evaluateXPathToNodes('*', assertNode, undefined, {
+			const asserts = evaluateXPathToNodes<Element>('*', assertNode, undefined, {
 				annotateAst: false,
 				language,
 			}).map((innerAssertNode) =>
@@ -622,7 +693,7 @@ function createAsserterForJsCodegen(
 			return (
 				xpath: string,
 				contextNode: Element,
-				variablesInScope: object,
+				_variablesInScope: { [s: string]: unknown },
 				namespaceResolver: (str: string) => string,
 				that
 			): JavaScriptCompiledXPathResult => {
@@ -963,7 +1034,7 @@ function createAsserterForJsCodegen(
 							}
 						),
 						`Expected XPath ${xpath} to resolve to the given XML. Expected ${results
-							.map((result) => new XMLSerializer().serializeToString(result))
+							.map((result) => new SlimdomXMLSerializer().serializeToString(result))
 							.join(' ')} to equal ${
 							parsedFragment.nodeType === parsedFragment.DOCUMENT_FRAGMENT_NODE
 								? parsedFragment.childNodes
@@ -1018,18 +1089,23 @@ function createAsserterForJsCodegen(
 
 function getExpressionBackendAsserterForTest(
 	baseUrl: string,
-	testCase,
+	testCase: Element,
 	language: Language,
-	annotateAst: boolean
+	annotateAst: boolean,
+	xmlSerializer?: SlimdomXMLSerializer
 ) {
-	const assertNode = evaluateXPathToFirstNode('./result/*', testCase, undefined, {
+	const assertNode = evaluateXPathToFirstNode<Element>('./result/*', testCase, undefined, {
 		annotateAst,
 	});
-	return createAsserterForExpression(baseUrl, assertNode, language, annotateAst);
+	return createAsserterForExpression(baseUrl, assertNode, language, annotateAst, xmlSerializer);
 }
 
-function getJsCodegenBackendAsserterForTest(baseUrl: string, testCase, language) {
-	const assertNode = evaluateXPathToFirstNode('./result/*', testCase, undefined, {
+function getJsCodegenBackendAsserterForTest(
+	baseUrl: string,
+	testCase: Element,
+	language: Language
+) {
+	const assertNode = evaluateXPathToFirstNode<Element>('./result/*', testCase, undefined, {
 		annotateAst: false,
 	});
 	return createAsserterForJsCodegen(baseUrl, assertNode, language);
@@ -1037,13 +1113,13 @@ function getJsCodegenBackendAsserterForTest(baseUrl: string, testCase, language)
 
 const registeredModuleURIByFileName = Object.create(null);
 
-function getTestName(testCase) {
+function getTestName(testCase: Element) {
 	return evaluateXPathToString('./@name', testCase, undefined, {
 		annotateAst: false,
 	});
 }
 
-function getTestDescription(testSetName, testName, testCase) {
+function getTestDescription(testSetName: string, testName: string, testCase: Element) {
 	return (
 		testSetName +
 		'~' +
@@ -1060,7 +1136,7 @@ function getTestDescription(testSetName, testName, testCase) {
 	);
 }
 
-function loadModule(testCase, baseUrl) {
+function loadModule(testCase: Element, baseUrl: string) {
 	// Load the module within the try to catch any errors from the module
 	const moduleImports = evaluateXPathToArray(
 		'array{module!map{"uri": @uri/string(), "file": $baseUrl || "/" || @file/string()}}',
@@ -1105,7 +1181,7 @@ describe('qt3 test set', () => {
 		const testSetName = evaluateXPathToString('/test-set/@name', testSet);
 
 		// Find all the tests we can run
-		const testCases: Node[] = evaluateXPathToNodes(ALL_TESTS_QUERY, testSet);
+		const testCases = evaluateXPathToNodes<Element>(ALL_TESTS_QUERY, testSet);
 		if (!testCases.length) {
 			return;
 		}
@@ -1145,7 +1221,8 @@ describe('qt3 test set', () => {
 									baseUrl,
 									testCase,
 									language,
-									annotateAst
+									annotateAst,
+									new slimdom.XMLSerializer()
 								);
 
 								asserter(
diff --git a/test/specs/parsing/functions/builtinFunctions.sequences.tests.ts b/test/specs/parsing/functions/builtinFunctions.sequences.tests.ts
index 38c5af01..a267217b 100644
--- a/test/specs/parsing/functions/builtinFunctions.sequences.tests.ts
+++ b/test/specs/parsing/functions/builtinFunctions.sequences.tests.ts
@@ -956,5 +956,53 @@ describe('Functions and operators on sequences', () => {
 					'FORG0006'
 				));
 		});
+
+		describe('fn:serialize', () => {
+			it('can serialize a single element', () =>
+				chai.assert.equal(
+					evaluateXPathToString(
+						'serialize(.)',
+						documentNode.createElement('ele'),
+						null,
+						null,
+						{ xmlSerializer: new slimdom.XMLSerializer() }
+					),
+					'<ele/>'
+				));
+
+			it('can serialize a text node', () =>
+				chai.assert.equal(
+					evaluateXPathToString(
+						'serialize(.)',
+						documentNode.createTextNode('A piece of text'),
+						null,
+						null,
+						{ xmlSerializer: new slimdom.XMLSerializer() }
+					),
+					'A piece of text'
+				));
+
+			it('throws a readable error when no xmlSerializer is passed', () =>
+				chai.assert.throws(
+					() =>
+						evaluateXPathToString(
+							'serialize(.)',
+							documentNode.createTextNode('A piece of text'),
+							null,
+							null,
+							{ xmlSerializer: null }
+						),
+					'serialize() called but no xmlSerializer set in execution parameters.'
+				));
+
+			it('throws a readable error when no nodes are passed', () =>
+				chai.assert.throws(
+					() =>
+						evaluateXPathToString('serialize(42)', null, null, null, {
+							xmlSerializer: new slimdom.XMLSerializer(),
+						}),
+					'Expected argument to fn:serialize to resolve to a sequence of Nodes'
+				));
+		});
 	});
 });