blob: 6af30958766381349fc73c46f3c3eb632a31d8f2 [file] [log] [blame]
Akron797e8072020-02-13 07:59:40 +01001use strict;
2use warnings;
3use File::Basename 'dirname';
4use File::Spec::Functions qw/catfile/;
Peter Harders1c5ce152020-07-22 18:02:50 +02005use Encode qw!encode_utf8 decode_utf8 encode!;
Akron2a60c532020-02-13 15:52:18 +01006use IO::Uncompress::Unzip qw(unzip $UnzipError);
Akron797e8072020-02-13 07:59:40 +01007
8use Test::More;
9use Test::Output;
Akrond89ef822020-02-17 12:42:09 +010010use Test::XML::Loy;
Peter Harders42e18a62020-07-21 02:43:26 +020011
12use FindBin;
13BEGIN {
14 unshift @INC, "$FindBin::Bin/../lib";
15};
Peter Harders1c5ce152020-07-22 18:02:50 +020016
Akron5fb5e8d2020-07-23 17:45:13 +020017use Test::KorAP::XML::TEI qw!korap_tempfile!;
Peter Harders57c884e2020-07-16 01:28:52 +020018
Akron797e8072020-02-13 07:59:40 +010019my $f = dirname(__FILE__);
20my $script = catfile($f, '..', 'script', 'tei2korapxml');
21ok(-f $script, 'Script found');
22
Akrond949e182020-02-14 12:23:57 +010023stdout_like(
Akron797e8072020-02-13 07:59:40 +010024 sub { system('perl', $script, '--help') },
Akrond949e182020-02-14 12:23:57 +010025 qr!This\s*program\s*is\s*usually\s*called\s*from\s*inside\s*another\s*script\.!,
Akron797e8072020-02-13 07:59:40 +010026 'Help'
27);
28
Akrond949e182020-02-14 12:23:57 +010029stdout_like(
30 sub { system('perl', $script, '--version') },
31 qr!tei2korapxml - v\d+?\.\d+?!,
32 'Version'
33);
34
35
Akron2a60c532020-02-13 15:52:18 +010036# Load example file
37my $file = catfile($f, 'data', 'goe_sample.i5.xml');
Peter Harders57c884e2020-07-16 01:28:52 +020038
Akron5fb5e8d2020-07-23 17:45:13 +020039my ($fh, $outzip) = korap_tempfile('script_out');
Akron2a60c532020-02-13 15:52:18 +010040
41# Generate zip file (unportable!)
42stderr_like(
Peter Hardersf9c51242020-07-21 02:37:44 +020043 sub { `cat '$file' | perl '$script' -ti > '$outzip'` },
Peter Harders57c884e2020-07-16 01:28:52 +020044# approaches for working with $fh (also better use OO interface then)
45# sub { open STDOUT, '>&', $fh; system("cat '$file' | perl '$script'") },
46# sub { open(my $pipe, "cat '$file' | perl '$script'|"); while(<$pipe>){$fh->print($_)}; $fh->close },
47# sub {
48# defined(my $pid = fork) or die "fork: $!";
49# if (!$pid) {
50# open STDOUT, '>&', $fh;
51# exec "cat '$file' | perl '$script'"
52# }
53# waitpid $pid, 0;
54# $fh->close;
55# },
Akron2a60c532020-02-13 15:52:18 +010056 qr!tei2korapxml: .*? text_id=GOE_AGA\.00000!,
57 'Processing'
58);
59
Akron85717512020-07-08 11:19:19 +020060ok(-e $outzip, "File $outzip exists");
61
Akron2a60c532020-02-13 15:52:18 +010062# Uncompress GOE/header.xml from zip file
63my $zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/header.xml');
64
65ok($zip, 'Zip-File is created');
66
Peter Harders57c884e2020-07-16 01:28:52 +020067# TODO: check wrong encoding in header-files (compare with input document)!
Akron2a60c532020-02-13 15:52:18 +010068# Read GOE/header.xml
69my $header_xml = '';
70$header_xml .= $zip->getline while !$zip->eof;
71ok($zip->close, 'Closed');
72
Akrond89ef822020-02-17 12:42:09 +010073my $t = Test::XML::Loy->new($header_xml);
Akron2a60c532020-02-13 15:52:18 +010074
Akrond89ef822020-02-17 12:42:09 +010075$t->text_is('korpusSigle', 'GOE', 'korpusSigle')
76 ->text_is('h\.title[type=main]', 'Goethes Werke', 'h.title')
77 ->text_is('h\.author', 'Goethe, Johann Wolfgang von', 'h.author')
78 ->text_is('pubDate[type=year]', '1982', 'pubDate');
Akron2a60c532020-02-13 15:52:18 +010079
Akron68966082020-02-13 15:52:18 +010080
Akron2a60c532020-02-13 15:52:18 +010081# Uncompress GOE/AGA/header.xml from zip file
82$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/header.xml');
83
84ok($zip, 'Zip-File is found');
85
86# Read GOE/AGA/header.xml
87$header_xml = '';
88$header_xml .= $zip->getline while !$zip->eof;
89ok($zip->close, 'Closed');
90
Akrond89ef822020-02-17 12:42:09 +010091$t = Test::XML::Loy->new($header_xml);
Akron2a60c532020-02-13 15:52:18 +010092
Akrond89ef822020-02-17 12:42:09 +010093$t->text_is('dokumentSigle', 'GOE/AGA', 'dokumentSigle')
94 ->text_is('d\.title', 'Goethe: Autobiographische Schriften II, (1817-1825, 1832)', 'd.title')
95 ->text_is('creatDate', '1820-1822', 'creatDate');
Akron2a60c532020-02-13 15:52:18 +010096
97# Uncompress GOE/AGA/00000/header.xml from zip file
98$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/header.xml');
99
100ok($zip, 'Zip-File is found');
101
102# Read GOE/AGA/00000/header.xml
103$header_xml = '';
104$header_xml .= $zip->getline while !$zip->eof;
105ok($zip->close, 'Closed');
106
Akrond89ef822020-02-17 12:42:09 +0100107$t = Test::XML::Loy->new($header_xml);
108$t->text_is('textSigle', 'GOE/AGA.00000', 'textSigle')
109 ->text_is('analytic > h\.title[type=main]', 'Campagne in Frankreich', 'h.title');
Akron2a60c532020-02-13 15:52:18 +0100110
111# Uncompress GOE/AGA/00000/data.xml from zip file
112$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/data.xml');
113
114ok($zip, 'Zip-File is found');
115
116# Read GOE/AGA/00000/data.xml
117my $data_xml = '';
118$data_xml .= $zip->getline while !$zip->eof;
119ok($zip->close, 'Closed');
120
Akrond89ef822020-02-17 12:42:09 +0100121$t = Test::XML::Loy->new($data_xml);
122$t->attr_is('raw_text', 'docid', 'GOE_AGA.00000', 'text id')
123 ->text_like('raw_text > text', qr!^Campagne in Frankreich 1792.*?uns allein begl.*cke\.$!, 'text content');
Akron2a60c532020-02-13 15:52:18 +0100124
125# Uncompress GOE/AGA/00000/struct/structure.xml from zip file
126$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/struct/structure.xml');
127
128ok($zip, 'Zip-File is found');
129
130# Read GOE/AGA/00000/struct/structure.xml
131my $struct_xml = '';
132$struct_xml .= $zip->getline while !$zip->eof;
Peter Harders57c884e2020-07-16 01:28:52 +0200133
Akron2a60c532020-02-13 15:52:18 +0100134ok($zip->close, 'Closed');
135
Akrond89ef822020-02-17 12:42:09 +0100136$t = Test::XML::Loy->new($struct_xml);
137$t->text_is('span[id=s3] *[name=type]', 'Autobiographie', 'text content');
Akron797e8072020-02-13 07:59:40 +0100138
Peter Harders42e18a62020-07-21 02:43:26 +0200139$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens.xml');
140ok(!$zip, 'External not generated');
Akroneac374d2020-07-07 09:00:44 +0200141
142# Uncompress GOE/AGA/00000/base/tokens_aggressive.xml from zip file
143$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens_aggressive.xml');
144
145# Read GOE/AGA/00000/base/tok.xml
146my $tokens_xml = '';
147$tokens_xml .= $zip->getline while !$zip->eof;
148ok($zip->close, 'Closed');
149
150$t = Test::XML::Loy->new($tokens_xml);
151$t->attr_is('spanList span:nth-child(1)', 'to', 8);
152
153$t->attr_is('spanList span#t_1', 'from', 9);
154$t->attr_is('spanList span#t_1', 'to', 11);
155
156$t->attr_is('spanList span#t_67', 'from', 427);
157$t->attr_is('spanList span#t_67', 'to', 430);
158
159$t->attr_is('spanList span#t_214', 'from', 1209);
160$t->attr_is('spanList span#t_214', 'to', 1212);
161
162$t->element_count_is('spanList span', 227);
163
164
165# Uncompress GOE/AGA/00000/base/tokens_conservative.xml from zip file
166$zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens_conservative.xml');
167
Akron8b511f92020-07-09 17:28:08 +0200168$tokens_xml = '';
169$tokens_xml .= $zip->getline while !$zip->eof;
170ok($zip->close, 'Closed');
171
172$t = Test::XML::Loy->new($tokens_xml);
173$t->attr_is('spanList span:nth-child(1)', 'to', 8);
174
175$t->attr_is('spanList span#t_1', 'from', 9);
176$t->attr_is('spanList span#t_1', 'to', 11);
177
178$t->attr_is('spanList span#t_67', 'from', 427);
179$t->attr_is('spanList span#t_67', 'to', 430);
180
181$t->attr_is('spanList span#t_214', 'from', 1209);
182$t->attr_is('spanList span#t_214', 'to', 1212);
183
184$t->element_count_is('spanList span', 227);
185
Peter Harders42e18a62020-07-21 02:43:26 +0200186
Akron8b511f92020-07-09 17:28:08 +0200187# Tokenize with external tokenizer
188my $cmd = catfile($f, 'cmd', 'tokenizer.pl');
189
Akron5fb5e8d2020-07-23 17:45:13 +0200190my ($fh2, $outzip2) = korap_tempfile('script_out2');
Peter Hardersb1227172020-07-21 02:12:10 +0200191
Akron8b511f92020-07-09 17:28:08 +0200192stderr_like(
Peter Harders42e18a62020-07-21 02:43:26 +0200193 sub { `cat '$file' | perl '$script' -tc='perl $cmd' > '$outzip2'` },
Akron8b511f92020-07-09 17:28:08 +0200194 qr!tei2korapxml: .*? text_id=GOE_AGA\.00000!,
195 'Processing'
196);
197
Peter Harders71f072b2020-07-15 14:15:01 +0200198# Uncompress GOE/AGA/00000/base/tokens.xml from zip file
Peter Hardersb1227172020-07-21 02:12:10 +0200199$zip = IO::Uncompress::Unzip->new($outzip2, Name => 'GOE/AGA/00000/base/tokens.xml');
Peter Harders42e18a62020-07-21 02:43:26 +0200200ok($zip, 'Found');
201ok(!$zip->eof, 'Readable');
Akron8b511f92020-07-09 17:28:08 +0200202
203# Read GOE/AGA/00000/base/tokens.xml
Akroneac374d2020-07-07 09:00:44 +0200204$tokens_xml = '';
205$tokens_xml .= $zip->getline while !$zip->eof;
206ok($zip->close, 'Closed');
207
208$t = Test::XML::Loy->new($tokens_xml);
209$t->attr_is('spanList span:nth-child(1)', 'to', 8);
210
211$t->attr_is('spanList span#t_1', 'from', 9);
212$t->attr_is('spanList span#t_1', 'to', 11);
213
214$t->attr_is('spanList span#t_67', 'from', 427);
215$t->attr_is('spanList span#t_67', 'to', 430);
216
217$t->attr_is('spanList span#t_214', 'from', 1209);
218$t->attr_is('spanList span#t_214', 'to', 1212);
219
220$t->element_count_is('spanList span', 227);
221
Peter Harders71f072b2020-07-15 14:15:01 +0200222
Akron5fb5e8d2020-07-23 17:45:13 +0200223my ($fh3, $outzip3) = korap_tempfile('script_out3');
Peter Hardersb1227172020-07-21 02:12:10 +0200224
225
Peter Harders71f072b2020-07-15 14:15:01 +0200226# ~ test conservative tokenization ~
227
228$file = catfile($f, 'data', 'text_with_blanks.i5.xml');
229
230stderr_like(
Peter Hardersf9c51242020-07-21 02:37:44 +0200231 sub { `cat '$file' | perl '$script' --ti > '$outzip3'` },
Peter Harders71f072b2020-07-15 14:15:01 +0200232 qr!tei2korapxml: .*? text_id=CORP_DOC.00001!,
233 'Processing'
234);
235
Peter Hardersb1227172020-07-21 02:12:10 +0200236ok(-e $outzip3, "File $outzip3 exists");
Peter Harders71f072b2020-07-15 14:15:01 +0200237
Peter Hardersb1227172020-07-21 02:12:10 +0200238$zip = IO::Uncompress::Unzip->new($outzip3, Name => 'CORP/DOC/00001/base/tokens_conservative.xml');
Peter Harders71f072b2020-07-15 14:15:01 +0200239
240ok($zip, 'Zip-File is created');
241
242my $cons = '';
243$cons .= $zip->getline while !$zip->eof;
244ok($zip->close, 'Closed');
245
246$t = Test::XML::Loy->new($cons);
247$t->attr_is('spanList span:nth-child(1)', 'to', 6);
248
249$t->attr_is('spanList span#t_1', 'from', 7);
250$t->attr_is('spanList span#t_1', 'to', 9);
251
252$t->attr_is('spanList span#t_3', 'from', 12);
253$t->attr_is('spanList span#t_3', 'to', 16);
254
255$t->attr_is('spanList span#t_9', 'from', 36);
256$t->attr_is('spanList span#t_9', 'to', 37);
257
258$t->attr_is('spanList span#t_13', 'from', 44);
259$t->attr_is('spanList span#t_13', 'to', 45); # "
260
261$t->attr_is('spanList span#t_14', 'from', 45); # twenty-two
262$t->attr_is('spanList span#t_14', 'to', 55);
263
264$t->attr_is('spanList span#t_15', 'from', 55); # "
265$t->attr_is('spanList span#t_15', 'to', 56);
266
267$t->attr_is('spanList span#t_19', 'from', 66);
268$t->attr_is('spanList span#t_19', 'to', 67);
269
270$t->element_count_is('spanList span', 20);
271
272
273# ~ test aggressive tokenization ~
274
Peter Hardersb1227172020-07-21 02:12:10 +0200275$zip = IO::Uncompress::Unzip->new($outzip3, Name => 'CORP/DOC/00001/base/tokens_aggressive.xml');
Peter Harders71f072b2020-07-15 14:15:01 +0200276
277ok($zip, 'Zip-File is created');
278
279my $aggr = '';
280$aggr .= $zip->getline while !$zip->eof;
281ok($zip->close, 'Closed');
282
283$t = Test::XML::Loy->new($aggr);
284
285$t->attr_is('spanList span:nth-child(1)', 'to', 6);
286
287$t->attr_is('spanList span#t_1', 'from', 7);
288$t->attr_is('spanList span#t_1', 'to', 9);
289
290$t->attr_is('spanList span#t_3', 'from', 12);
291$t->attr_is('spanList span#t_3', 'to', 16);
292
293$t->attr_is('spanList span#t_9', 'from', 36);
294$t->attr_is('spanList span#t_9', 'to', 37);
295
296$t->attr_is('spanList span#t_13', 'from', 44);
297$t->attr_is('spanList span#t_13', 'to', 45); # "
298
299$t->attr_is('spanList span#t_14', 'from', 45); # twenty
300$t->attr_is('spanList span#t_14', 'to', 51);
301
302$t->attr_is('spanList span#t_15', 'from', 51); # -
303$t->attr_is('spanList span#t_15', 'to', 52);
304
305$t->attr_is('spanList span#t_16', 'from', 52); # two
306$t->attr_is('spanList span#t_16', 'to', 55);
307
308$t->attr_is('spanList span#t_17', 'from', 55); # "
309$t->attr_is('spanList span#t_17', 'to', 56);
310
311$t->attr_is('spanList span#t_21', 'from', 66);
312$t->attr_is('spanList span#t_21', 'to', 67);
313
314$t->element_count_is('spanList span', 22);
315
316
Peter Harders42e18a62020-07-21 02:43:26 +0200317subtest 'Check Tokenization Flags' => sub {
318
319 # Get external tokenizer
320 my $f = dirname(__FILE__);
321 my $cmd = catfile($f, 'cmd', 'tokenizer.pl');
322
323 # Load example file
324 my $file = catfile($f, 'data', 'goe_sample.i5.xml');
325
326 my ($fh, $outzip) = korap_tempfile('script_tokflags');
327
328 # Generate zip file (unportable!)
329 stderr_like(
330 sub { `cat '$file' | perl '$script' -ti -tc 'perl $cmd' > '$outzip'` },
331 qr!tei2korapxml: .*? text_id=GOE_AGA\.00000!,
332 'Processing'
333 );
334
335 ok(-e $outzip, "File $outzip exists");
336
337 $zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens_aggressive.xml');
338 ok($zip, 'Aggressive generated');
339 $zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens_conservative.xml');
340 ok($zip, 'Conservative generated');
341 $zip = IO::Uncompress::Unzip->new($outzip, Name => 'GOE/AGA/00000/base/tokens.xml');
342 ok($zip, 'External generated');
343};
344
Peter Harders1c5ce152020-07-22 18:02:50 +0200345
346subtest 'Test utf-8 handling' => sub {
347
348 # Load template file
349 $file = catfile($f, 'data', 'template.i5.xml');
350 my $tpl = '';
351 {
352 open($fh, $file);
353 $tpl .= <$fh> while !eof($fh);
354 close($fh);
355 }
356
357 # Introduce invalid utf-8 characters
358 my $text_sigle;
359 { no warnings;
360 # $text_sigle printed to file, without encoding: Aþƒ¿¿¿¿¿A_Bþƒ¿¿¿¿¿B.Cþƒ¿¿¿¿¿C
361 # the utf8-sequence 'þƒ¿¿¿¿¿' encodes 32 bit of data (see 0x7FFF_FFFF in perlunicode)
362 $text_sigle = "A\x{FFFF_FFFF}A_B\x{FFFF_FFFF}B.C\x{FFFF_FFFF}C" }
363 # If CHECK is 0, encoding and decoding replace any malformed character with a substitution character.
364 # � = substitution character
365 my $text_sigle_lax = encode_utf8($text_sigle);
366 my $text_sigle_esc = encode('UTF-8', $text_sigle);
367
368 is(length($text_sigle), 11); # A�A_B�B.C�C (char string => length(�) = 1)
369 is(length($text_sigle_lax), 29); # Aþƒ¿¿¿¿¿A_Bþƒ¿¿¿¿¿B.Cþƒ¿¿¿¿¿C (byte string)
370 is(length($text_sigle_esc), 17); # A�A_B�B.C�C (byte string => length(�) = 3)
371
372 { no warnings;
373 $tpl =~ s!\[KORPUSSIGLE\]!A\x{FFFF_FFFF}A!;
374 $tpl =~ s!\[DOKUMENTSIGLE\]!A\x{FFFF_FFFF}A_B\x{FFFF_FFFF}B!;
375 $tpl =~ s!\[TEXT\]!<p>d\x{FFFF_FFFF}d e\x{FFFF_FFFF}e f\x{FFFF_FFFF}f</p>! }
376 $tpl =~ s!\[TEXTSIGLE\]!$text_sigle!;
377
378 my ($fh, $tplfile) = korap_tempfile('script_out4');
379 binmode($fh);
380 print $fh encode_utf8($tpl); # => text_id=Aþƒ¿¿¿¿¿A_Bþƒ¿¿¿¿¿B.Cþƒ¿¿¿¿¿C
381 close($fh);
382
383 my (undef, $outzip) = korap_tempfile('script_out5');
384
385 binmode STDERR, qw{ :encoding(UTF-8) }; # because output 'textid=...' goes to STDERR (see script/tei2korapxml)
386
387 stderr_like(
388 sub { `cat '$tplfile' | perl '$script' -ti > '$outzip'` },
389 qr!tei2korapxml: .*? text_id=$text_sigle_lax!, # see above: print $fh encode_utf8($tpl);
390 );
391};
392
Akrone68ec0c2020-07-28 18:06:19 +0200393
394subtest 'Check Inline annotations' => sub {
395
396 # Load example file
397 my $file = catfile($f, 'data', 'goe_sample_tagged.i5.xml');
398
399 my ($fh, $outzip) = korap_tempfile('script_tagged');
400
401 # Generate zip file (unportable!)
402 stderr_like(
403 sub { `cat '$file' | KORAPXMLTEI_INLINE=1 perl '$script' > '$outzip'` },
404 qr!tei2korapxml: .*? text_id=GOE_AGA\.00000!,
405 'Processing'
406 );
407
408 ok(-e $outzip, "File $outzip exists");
409
410 my $zip = IO::Uncompress::Unzip->new(
411 $outzip,
412 Name => 'GOE/AGA/00000/tokens/morpho.xml'
413 );
414 ok($zip, 'Inline annotations');
415
416 my $tokens;
417 $tokens .= $zip->getline while !$zip->eof;
418 ok($zip->close, 'Closed');
419
420 my $t = Test::XML::Loy->new($tokens);
421
422 $t->attr_is('layer', 'docid', 'GOE_AGA.00000')
423 ->attr_is('spanList span:nth-child(1)', 'id', 's0')
424 ->attr_is('spanList span:nth-child(1)', 'from', '75')
425 ->attr_is('spanList span:nth-child(1)', 'to', '81')
426 ->attr_is('spanList span:nth-child(1)', 'l', '7')
427
428 ->attr_is('span#s0 > fs', 'type', 'lex')
429 ->attr_is('span#s0 > fs', 'xmlns', 'http://www.tei-c.org/ns/1.0')
430 ->attr_is('span#s0 > fs > f > fs > f:nth-child(1)', 'name', 'pos')
431 ->text_is('span#s0 > fs > f > fs > f:nth-child(1)', 'A')
432 ->attr_is('span#s0 > fs > f > fs > f:nth-child(2)', 'name', 'msd')
433 ->text_is('span#s0 > fs > f > fs > f:nth-child(2)', '@NH')
434
435 ->attr_is('span#s25', 'from', '259')
436 ->attr_is('span#s25', 'to', '263')
437 ->attr_is('span#s25', 'l', '7')
438 ->attr_is('span#s25 > fs > f > fs > f:nth-child(1)', 'name', 'pos')
439 ->text_is('span#s25 > fs > f > fs > f:nth-child(1)', 'PRON')
440 ->attr_is('span#s25 > fs > f > fs > f:nth-child(2)', 'name', 'msd')
441 ->text_is('span#s25 > fs > f > fs > f:nth-child(2)', '@NH')
442
443 ->attr_is('span#s58', 'from', '495')
444 ->attr_is('span#s58', 'to', '500')
445 ->attr_is('span#s58', 'l', '7')
446 ->attr_is('span#s58 > fs > f > fs > f:nth-child(1)', 'name', 'pos')
447 ->text_is('span#s58 > fs > f > fs > f:nth-child(1)', 'N')
448 ->attr_is('span#s58 > fs > f > fs > f:nth-child(2)', 'name', 'msd')
449 ->text_is('span#s58 > fs > f > fs > f:nth-child(2)', '@NH')
450
451 ->attr_is('span#s119', 'from', '914')
452 ->attr_is('span#s119', 'to', '925')
453 ->attr_is('span#s119', 'l', '7')
454 ->attr_is('span#s119 > fs > f > fs > f:nth-child(1)', 'name', 'pos')
455 ->text_is('span#s119 > fs > f > fs > f:nth-child(1)', 'A')
456 ->attr_is('span#s119 > fs > f > fs > f:nth-child(2)', 'name', 'msd')
457 ->text_is('span#s119 > fs > f > fs > f:nth-child(2)', '@NH')
458 ->element_exists_not('span#s120')
459 ;
460};
461
462
Akron797e8072020-02-13 07:59:40 +0100463done_testing;