[LON-CAPA-cvs] cvs: loncom /interface loncommon.pm /node.js/axe AxeRunner.pm lonaxe.pl
raeburn
raeburn at source.lon-capa.org
Thu Jul 30 14:26:18 EDT 2026
raeburn Thu Jul 30 18:26:18 2026 EDT
Modified files:
/loncom/node.js/axe lonaxe.pl AxeRunner.pm
/loncom/interface loncommon.pm
Log:
- Accessibility testing using axe-core and puppeteer (node.js) using a
headless chromium web browser.
- Display information about URLs which were in compliance, but which had
incomplete tests, and include access to details about those tests.
- For non-compliant URLs, detailed information about incomplete tests
appended to file containing details about non-compliance.
- In listing of recent accessibility tests, omit files with no content
and float the three tables (if present) alongside one another.
-------------- next part --------------
Index: loncom/node.js/axe/lonaxe.pl
diff -u loncom/node.js/axe/lonaxe.pl:1.5 loncom/node.js/axe/lonaxe.pl:1.6
--- loncom/node.js/axe/lonaxe.pl:1.5 Sat Jul 25 15:47:21 2026
+++ loncom/node.js/axe/lonaxe.pl Thu Jul 30 18:26:18 2026
@@ -1,6 +1,6 @@
#!/usr/bin/perl
$|=1;
-# $Id: lonaxe.pl,v 1.5 2026/07/25 15:47:21 raeburn Exp $
+# $Id: lonaxe.pl,v 1.6 2026/07/30 18:26:18 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -64,6 +64,8 @@
$outfile =~s/\.dat$/.txt/;
my $csvfile = $outfile;
$csvfile =~s/\.txt/.csv/;
+my $logfile = $filename;
+$logfile =~s/\.dat$/.log/;
my %perlvar=%{&LONCAPA::Configuration::read_conf('loncapa.conf')};
&Apache::lonlocal::get_language_handle();
@@ -170,7 +172,7 @@
my $lonhost = $perlvar{'lonHostID'};
my $spooldir = $perlvar{'lonAxeDir'};
my $hostname = &Apache::lonnet::hostname($lonhost);
-my ($handle,$csvlink,$detailslink,$showlevel,$lock);
+my ($handle,$csvlink,$detailslink,$uncertainlink,$showlevel,$lock);
if ($wcag =~ /^2(0|1|2)(a{1,3})$/) {
$showlevel = "WCAG 2.$1 ".uc($2);
} else {
@@ -183,16 +185,20 @@
res => 'Resource',
sta => 'Status',
vio => 'Violations',
+ inc => 'Incompletes',
cno => 'Could not open files for output',
nor => 'No resources found to check',
com => 'Number of resources in compliance',
+ unk => 'Number of resources in compliance, but with incomplete tests',
not => 'Number of resources not in compliance',
sum => 'Results summary (CSV format)',
det => 'Details for non-compliant resources (plain text)',
+ inf => 'Details for resources with incomplete tests only (plain text)',
err => 'Errors during accessibility checking',
lau => 'Launch of Accessibility checking failed',
pass => 'pass',
fail => 'fail',
+ skip => 'skip',
invalid => 'Invalid compliance level or invalid URL',
nolistener => 'No listener available to receive URL',
);
@@ -201,6 +207,7 @@
my $stem = $1;
$csvlink = "/axespool/$stem.csv";
$detailslink = "/axespool/$stem.txt";
+ $uncertainlink = "/axespool/$stem.log";
} else {
print &Apache::loncommon::end_page();
exit;
@@ -232,7 +239,7 @@
'<br /></div>'.
'<div style="padding:0;clear:both;margin:0;border:0"></div>';
my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin('',$number_of_files,$preamble);
- my (%errors, at passed, at failed);
+ my (%errors, at passed, at uncertain, at failed);
print "<br />";
if (open($fh,'<',$filename)) {
my @items;
@@ -242,9 +249,9 @@
}
close($fh);
if (@items) {
- my ($outfh,$csvfh);
- if ((open($outfh,'>',$outfile)) && (open($csvfh,'>',$csvfile))) {
- print $csvfh "\"$lt{'res'}\",\"$lt{'sta'}\",\"$lt{'vio'}\"\n";
+ my ($outfh,$csvfh,$logfh);
+ if ((open($outfh,'>',$outfile)) && (open($csvfh,'>',$csvfile)) && (open($logfh,'>',$logfile))) {
+ print $csvfh "\"$lt{'res'}\",\"$lt{'sta'}\",\"$lt{'vio'}\",\"$lt{'inc'}\"\n";
foreach my $item (@items) {
my $querystr = '?inhibitmenu=yes';
my $url;
@@ -258,28 +265,49 @@
} else {
$url = $privurl.$item;
}
- if ($url ne '') {
- my ($violations, at details) =
- &LONCAPA::AxeRunner::checkcompliance($protocol.'://'.$hostname.$url.$querystr);
+ if ($url ne '') {
+ my ($fails,$skips) = ('','');
+ my ($violations,$incompletes) =
+ &LONCAPA::AxeRunner::checkcompliance($protocol.'://'.$hostname.$url.$querystr,
+ \$fails,\$skips);
&Apache::lonhtmlcommon::Increment_PrgWin('',\%prog_state,'last resource');
if ($violations eq -1) {
push(@{$errors{'invalid'}},$url);
} elsif ($violations eq '') {
push(@{$errors{'nolistener'}},$url);
- } elsif ($violations eq 0) {
+ } elsif (($violations eq 0) && ($incompletes eq 0)) {
push(@passed,$url);
- print $csvfh "\"$url\",\"$lt{'pass'}\",\"0\"\n";
+ print $csvfh "\"$url\",\"$lt{'pass'}\",\"0\",\"0\"\n";
} else {
- push(@failed,$url);
- print $outfh &mt('[quant,_1,violation] for [_2]',
- $violations,$url)."\n".
- join("\n", at details)."\n--------------\n";
- print $csvfh "\"$url\",\"$lt{'fail'}\",\"$violations\"\n";
+ my $status;
+ if ($violations) {
+ $status = $lt{'fail'};
+ push(@failed,$url);
+ print $outfh &mt('[quant,_1,violation] for [_2]',
+ $violations,$url)."\n".
+ $fails;
+ if ($incompletes) {
+ print $outfh "\n--\n".
+ &mt('[quant,_1,incomplete] for [_2]',
+ $incompletes,$url)."\n".
+ $skips;
+ }
+ print $outfh "\n---------\n";
+ } elsif (($incompletes) && (!$violations)) {
+ $status = $lt{'skip'};
+ push(@uncertain,$url);
+ print $logfh &mt('[quant,_1,incomplete] for [_2]',
+ $incompletes,$url)."\n".
+ $skips."\n---------\n";
+
+ }
+ print $csvfh "\"$url\",\"$status\",\"$violations\",\"$incompletes\"\n";
}
}
}
close($csvfh);
close($outfh);
+ close($logfh);
} else {
print '<span class="LC_error">'.$lt{'cno'}.'</span><br />'."\n";
}
@@ -295,18 +323,22 @@
// ]]>
</script>
ENDCLOSE
- if (@passed || @failed) {
+ if (@passed || @failed || @uncertain) {
print '<h2 class="LC_heading_2">'.
&mt('Accessibility checking: compliance status (level [_1])',$showlevel).
'</h2>'."\n".
'<ul>'."\n".
'<li>'.$lt{'com'}.': '.scalar(@passed).'</li>'."\n".
+ '<li>'.$lt{'unk'}.': '.scalar(@uncertain).'</li>'."\n".
'<li>'.$lt{'not'}.': '.scalar(@failed).'</li>'."\n".
'</ul>'."\n".
'<p><a href="'.$csvlink.'">'.$lt{'sum'}.'</a></p>';
if (@failed) {
print '<p><a href="'.$detailslink.'">'.$lt{'det'}.'</a></p>';
}
+ if (@uncertain) {
+ print '<p><a href="'.$uncertainlink.'">'.$lt{'inf'}.'</a></p>';
+ }
}
if (keys(%errors)) {
print '<h2 class="LC_heading_2">'.$lt{'err'}.'</h2>'."\n";
Index: loncom/node.js/axe/AxeRunner.pm
diff -u loncom/node.js/axe/AxeRunner.pm:1.3 loncom/node.js/axe/AxeRunner.pm:1.4
--- loncom/node.js/axe/AxeRunner.pm:1.3 Thu Jul 30 01:45:05 2026
+++ loncom/node.js/axe/AxeRunner.pm Thu Jul 30 18:26:18 2026
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Accessibility Testing
#
-# $Id: AxeRunner.pm,v 1.3 2026/07/30 01:45:05 raeburn Exp $
+# $Id: AxeRunner.pm,v 1.4 2026/07/30 18:26:18 raeburn Exp $
#
# The LearningOnline Network with CAPA
#
@@ -89,22 +89,40 @@
}
sub checkcompliance {
- my ($url) = @_;
+ my ($url,$fails,$skips) = @_;
$url =~ s{^\s+|\s+$}{}g;
if ($url =~ m{^https?://[\w.]+/}) {
if ($pid) {
+ my ($count,$broken,$skip);
print $chld_in 'URL ' . $url . "\n";
- my $line = <$chld_out>;
- chomp($line);
- if ($line eq '0') {
- return ($line);
- } elsif ($line =~ /^(\d+)\s+/) {
- my $count = $1;
- $line =~s /^(\d+)\s+//;
- return ($count,Data::Dumper->Dump(JSON::DWIW::deserialize($line)));
+ while (my $line = <$chld_out>) {
+ chomp($line);
+ last if ($line eq '');
+ if ($line eq '0') {
+ $count = 0;
+ last;
+ } elsif ($line =~ /^(\d+),([IV]),/) {
+ my ($num,$type) = ($1,$2);
+ $line =~s/^\d+,[IV],//;
+ if ($line ne '') {
+ if (($type eq 'V') && (ref($fails))) {
+ $broken = $num;
+ $$fails = Data::Dumper->Dump(JSON::DWIW::deserialize($line));
+ } elsif (($type eq 'I') && (ref($skips))) {
+ $skip = $num;
+ $$skips = Data::Dumper->Dump(JSON::DWIW::deserialize($line));
+ }
+ }
+ }
+ last if (($broken ne '') && ($skip ne ''));
+ }
+ if ($count eq 0) {
+ return ($count,$count);
+ } else {
+ return ($broken,$skip);
}
} else {
- return;
+ return ();
}
} else {
return (-1);
@@ -142,11 +160,12 @@
use lib '/home/httpd/lib/perl/';
use LONCAPA::AxeRunner;
-
+
if (&LONCAPA::AxeRunner::launch ( $cookieid,$handle,$linkid,$hostname ) eq 'ok') {
- my ($violations,$details) =
- &LONCAPA::AxeRunner::checkcompliance( $url );
- &LONCAPA::AxeRunner::end();
+ my ($fails,$skips) = ('','');
+ my ($violations,$incompletes) =
+ &LONCAPA::AxeRunner::checkcompliance( $url, \$fails, \$skips );
+ &LONCAPA::AxeRunner::cleanup();
}
=head1 DESCRIPTION
@@ -159,7 +178,7 @@
=over 4
-=item LONCAPA::AxeRunner::launch ( $handle,$linkid,$hostname );
+=item LONCAPA::AxeRunner::launch( $handle,$linkid,$hostname );
=back
@@ -171,7 +190,7 @@
=over 4
-=item LONCAPA::AxeRunner::cleanup ();
+=item LONCAPA::AxeRunner::cleanup();
=back
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1532 loncom/interface/loncommon.pm:1.1533
--- loncom/interface/loncommon.pm:1.1532 Sun Jun 28 12:07:51 2026
+++ loncom/interface/loncommon.pm Thu Jul 30 18:26:18 2026
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1532 2026/06/28 12:07:51 raeburn Exp $
+# $Id: loncommon.pm,v 1.1533 2026/07/30 18:26:18 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -21081,7 +21081,7 @@
if ($spooltype eq 'axespool') {
$spooldir = $Apache::lonnet::perlvar{'lonAxeDir'};
$frag = 'axe';
- @okexts = qw(csv txt);
+ @okexts = qw(csv txt log);
} else {
$spooldir = $Apache::lonnet::perlvar{'lonPrtDir'};
$frag = 'printout';
@@ -21093,12 +21093,14 @@
zip => 'Zip File',
csv => 'CSV File',
txt => 'Text File',
+ log => 'Text File',
);
my %heading = &Apache::lonlocal::texthash (
zip => 'Recently generated printout zip files',
pdf => 'Recently generated printouts',
csv => 'Recently generated accessibility reports',
- txt => 'Recently generated accessibility data',
+ txt => 'Recently generated non-compliance',
+ log => 'Recently generated non-completion',
);
my $output;
if ($spooldir) {
@@ -21116,6 +21118,11 @@
my %count;
foreach my $filename (@files) {
my ($ext) = ($filename =~ m/($regexp)$/);
+ my ($cdev,$cino,$cmode,$cnlink,
+ $cuid,$cgid,$crdev,$csize,
+ $catime,$cmtime,$cctime,
+ $cblksize,$cblocks)=stat($spooldir.'/'.$filename);
+ next if (($csize == 0) && ($spooltype eq 'axespool'));
unless ($count{$ext}) {
$count{$ext} = 0;
$result{$ext} =
@@ -21127,10 +21134,6 @@
.'<th>'.&mt('File Size (Bytes)').'</th>'
.'</tr>'."\n";
}
- my ($cdev,$cino,$cmode,$cnlink,
- $cuid,$cgid,$crdev,$csize,
- $catime,$cmtime,$cctime,
- $cblksize,$cblocks)=stat($spooldir.'/'.$filename);
my $css_class = ($count{$ext} % 2)?'LC_odd_row':'LC_even_row';
$result{$ext} .=
'<tr class="'.$css_class.'" style="line-height: 170%;">'
@@ -21146,15 +21149,17 @@
if ($result{'zip'} || $result{'pdf'}) {
$output ='<hr />';
}
- } elsif ($spooltype eq 'axespool') {
- if ($result{'csv'} || $result{'txt'}) {
- $output ='<hr />';
- }
}
foreach my $ext (@okexts) {
if ($result{$ext}) {
+ if ($spooltype eq 'axespool') {
+ $output .= '<div class="LC_floatleft">';
+ }
$output .= $result{$ext}
.'</table>'."\n";
+ if ($spooltype eq 'axespool') {
+ $output .= '</div>';
+ }
}
}
}
More information about the LON-CAPA-cvs
mailing list