├── .gitattributes
├── .gitignore
├── A11y.csproj
├── A11y.sln
├── DriverManager.cs
├── DriversExecutables
├── AMD64
│ ├── MicrosoftWebDriver.exe
│ └── chromedriver.exe
└── X86
│ ├── MicrosoftWebDriver.exe
│ └── chromedriver.exe
├── EdgeA11yTools.cs
├── EdgeStrategy.cs
├── ElementConverter.cs
├── Interop.UIAutomationCore.dll
├── Javascript.cs
├── Program.cs
├── Properties
└── AssemblyInfo.cs
├── README.md
├── TestCaseResult.cs
├── TestData.cs
├── TestStrategy.cs
├── diff_runs.ps1
├── license.txt
├── packages.config
└── run.ps1
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # The output of the test
5 | scores.csv
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | build/
24 | bld/
25 | [Bb]in/
26 | [Oo]bj/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # DNX
47 | project.lock.json
48 | artifacts/
49 |
50 | *_i.c
51 | *_p.c
52 | *_i.h
53 | *.ilk
54 | *.meta
55 | *.obj
56 | *.pch
57 | *.pdb
58 | *.pgc
59 | *.pgd
60 | *.rsp
61 | *.sbr
62 | *.tlb
63 | *.tli
64 | *.tlh
65 | *.tmp
66 | *.tmp_proj
67 | *.log
68 | *.vspscc
69 | *.vssscc
70 | .builds
71 | *.pidb
72 | *.svclog
73 | *.scc
74 |
75 | # Chutzpah Test files
76 | _Chutzpah*
77 |
78 | # Visual C++ cache files
79 | ipch/
80 | *.aps
81 | *.ncb
82 | *.opensdf
83 | *.sdf
84 | *.cachefile
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # NuGet Packages
149 | *.nupkg
150 | # The packages folder can be ignored because of Package Restore
151 | **/packages/*
152 | # except build/, which is used as an MSBuild target.
153 | !**/packages/build/
154 | # Uncomment if necessary however generally it will be regenerated when needed
155 | #!**/packages/repositories.config
156 |
157 | # Windows Azure Build Output
158 | csx/
159 | *.build.csdef
160 |
161 | # Windows Azure Emulator
162 | efc/
163 | rfc/
164 |
165 | # Windows Store app package directory
166 | AppPackages/
167 |
168 | # Visual Studio cache files
169 | # files ending in .cache can be ignored
170 | *.[Cc]ache
171 | # but keep track of directories ending in .cache
172 | !*.[Cc]ache/
173 |
174 | # Others
175 | ClientBin/
176 | [Ss]tyle[Cc]op.*
177 | ~$*
178 | *~
179 | *.dbmdl
180 | *.dbproj.schemaview
181 | *.pfx
182 | *.publishsettings
183 | node_modules/
184 | orleans.codegen.cs
185 |
186 | # RIA/Silverlight projects
187 | Generated_Code/
188 |
189 | # Backup & report files from converting an old project file
190 | # to a newer Visual Studio version. Backup files are not needed,
191 | # because we have git ;-)
192 | _UpgradeReport_Files/
193 | Backup*/
194 | UpgradeLog*.XML
195 | UpgradeLog*.htm
196 |
197 | # SQL Server files
198 | *.mdf
199 | *.ldf
200 |
201 | # Business Intelligence projects
202 | *.rdl.data
203 | *.bim.layout
204 | *.bim_*.settings
205 |
206 | # Microsoft Fakes
207 | FakesAssemblies/
208 |
209 | # GhostDoc plugin setting file
210 | *.GhostDoc.xml
211 |
212 | # Node.js Tools for Visual Studio
213 | .ntvs_analysis.dat
214 |
215 | # Visual Studio 6 build log
216 | *.plg
217 |
218 | # Visual Studio 6 workspace options file
219 | *.opt
220 |
221 | # Visual Studio LightSwitch build output
222 | **/*.HTMLClient/GeneratedArtifacts
223 | **/*.DesktopClient/GeneratedArtifacts
224 | **/*.DesktopClient/ModelManifest.xml
225 | **/*.Server/GeneratedArtifacts
226 | **/*.Server/ModelManifest.xml
227 | _Pvt_Extensions
228 |
229 | # Paket dependency manager
230 | .paket/paket.exe
231 |
232 | # FAKE - F# Make
233 | .fake/
234 |
235 | # Powershell scripts used internally
236 | *int.ps1
237 |
238 | # Nuget executable
239 | nuget.exe
240 |
--------------------------------------------------------------------------------
/A11y.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}
8 | Exe
9 | Properties
10 | Microsoft.Edge.A11y
11 | Microsoft.Edge.A11y
12 | v4.5
13 | 512
14 | ..\
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 | False
39 | True
40 | A11y\Interop.UIAutomationCore.dll
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | packages\Selenium.WebDriver.2.52.0\lib\net40\WebDriver.dll
53 | True
54 |
55 |
56 | packages\Selenium.Support.2.52.0\lib\net40\WebDriver.Support.dll
57 | True
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Always
78 |
79 |
80 |
81 |
88 |
89 |
--------------------------------------------------------------------------------
/A11y.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.40629.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "A11y", "A11y.csproj", "{3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | Debug64|Any CPU = Debug64|Any CPU
13 | Debug64|x64 = Debug64|x64
14 | Release|Any CPU = Release|Any CPU
15 | Release|x64 = Release|x64
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug|x64.ActiveCfg = Debug|Any CPU
21 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug64|Any CPU.ActiveCfg = Debug|Any CPU
22 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug64|Any CPU.Build.0 = Debug|Any CPU
23 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Debug64|x64.ActiveCfg = Debug|Any CPU
24 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {3A2CB5B2-3FD1-4F87-8EBB-581562D1F954}.Release|x64.ActiveCfg = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(SolutionProperties) = preSolution
29 | HideSolutionNode = FALSE
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/DriverManager.cs:
--------------------------------------------------------------------------------
1 | using OpenQA.Selenium;
2 | using OpenQA.Selenium.Edge;
3 | using OpenQA.Selenium.Remote;
4 | using OpenQA.Selenium.Support.UI;
5 | using System;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Threading;
9 |
10 | namespace Microsoft.Edge.A11y
11 | {
12 | ///
13 | /// This is another wrapper around WebDriver. The reason this is used is to maintain
14 | /// compatibility with Edge internal testing tools.
15 | ///
16 | public class DriverManager
17 | {
18 | private RemoteWebDriver _driver;
19 | private TimeSpan _searchTimeout;
20 |
21 | ///
22 | /// Only ctor
23 | ///
24 | /// How long to search for elements
25 | public DriverManager(TimeSpan searchTimeout)
26 | {
27 | try
28 | {
29 | _driver = new EdgeDriver(EdgeDriverService.CreateDefaultService(DriverExecutablePath, "MicrosoftWebDriver.exe", 17556));
30 | }
31 | catch (InvalidOperationException)
32 | {
33 | Console.WriteLine("Unable to start a WebDriver session. Ensure that the previous server window is closed.");
34 | Environment.Exit(1);
35 | }
36 | _searchTimeout = searchTimeout;
37 | }
38 |
39 | ///
40 | /// The root directory of the A11y project
41 | ///
42 | public static string ProjectRootFolder
43 | {
44 | get
45 | {
46 | return new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.FullName;
47 | }
48 | }
49 |
50 | ///
51 | /// The path to the WebDriver executables for Edge
52 | ///
53 | private static string DriverExecutablePath
54 | {
55 | get
56 | {
57 | //TODO support for linux/mac paths
58 | return Path.Combine(ProjectRootFolder, Environment.Is64BitOperatingSystem
59 | ? "DriversExecutables\\AMD64"
60 | : "DriversExecutables\\X86");
61 | }
62 | }
63 |
64 | ///
65 | /// Navigate to url
66 | ///
67 | ///
68 | public void NavigateToUrl(string url)
69 | {
70 | _driver.Navigate().GoToUrl(url);
71 | }
72 |
73 | ///
74 | /// Execute a script on the current page
75 | ///
76 | /// The script
77 | /// The timeout in seconds
78 | /// How long to wait before executing the script in milliseconds
79 | /// Parameters to pass to the script
80 | ///
81 | public object ExecuteScript(string script, int timeout, int additionalSleep = 0, params object[] args)
82 | {
83 | Thread.Sleep(additionalSleep);
84 |
85 | var wait = new WebDriverWait(_driver, new TimeSpan(0, 0, 0, timeout));
86 | wait.Until(driver => ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));
87 |
88 | var js = (IJavaScriptExecutor)_driver;
89 | return js.ExecuteScript(script, args);
90 | }
91 |
92 | ///
93 | /// Send the given keys to the element with the given id
94 | ///
95 | /// The element's id
96 | /// The keys to send
97 | public void SendKeys(string elementId, string keys)
98 | {
99 | _driver.FindElement(By.Id(elementId)).SendKeys(keys);
100 | }
101 |
102 | public Screenshot GetScreenshot()
103 | {
104 | return _driver.GetScreenshot();
105 | }
106 |
107 | ///
108 | /// Close the driver
109 | ///
110 | internal void Close()
111 | {
112 | if (null != _driver)
113 | {
114 | try
115 | {
116 | _driver.Quit();
117 | _driver.Dispose();
118 | }
119 | catch (Exception)
120 | {
121 | // Don't throw here
122 | }
123 | _driver = null;
124 | }
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/DriversExecutables/AMD64/MicrosoftWebDriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftEdge/A11y/926dcaedcb5c9f45f0d4e10ae09942882483c74e/DriversExecutables/AMD64/MicrosoftWebDriver.exe
--------------------------------------------------------------------------------
/DriversExecutables/AMD64/chromedriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftEdge/A11y/926dcaedcb5c9f45f0d4e10ae09942882483c74e/DriversExecutables/AMD64/chromedriver.exe
--------------------------------------------------------------------------------
/DriversExecutables/X86/MicrosoftWebDriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftEdge/A11y/926dcaedcb5c9f45f0d4e10ae09942882483c74e/DriversExecutables/X86/MicrosoftWebDriver.exe
--------------------------------------------------------------------------------
/DriversExecutables/X86/chromedriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftEdge/A11y/926dcaedcb5c9f45f0d4e10ae09942882483c74e/DriversExecutables/X86/chromedriver.exe
--------------------------------------------------------------------------------
/EdgeA11yTools.cs:
--------------------------------------------------------------------------------
1 | using Interop.UIAutomationCore;
2 | using OpenQA.Selenium;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Drawing.Imaging;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using static Microsoft.Edge.A11y.ElementConverter;
10 |
11 | namespace Microsoft.Edge.A11y
12 | {
13 | ///
14 | /// A set of helper methods which wrap accessibility APIs
15 | ///
16 | static class EdgeA11yTools
17 | {
18 | const int RETRIES = 5;
19 | const int RECURSIONDEPTH = 10;
20 |
21 | public const string ExceptionMessage = "Currently only Edge is supported";
22 |
23 | ///
24 | /// This is used to find the DOM of the browser, which is used as the starting
25 | /// point when searching for elements.
26 | ///
27 | /// How many times we have already retried
28 | /// The browser, or null if it cannot be found
29 | public static IUIAutomationElement FindBrowserDocument(int retries)
30 | {
31 | var uia = new CUIAutomation8();
32 |
33 | return FindBrowserDocumentRecurser(uia.GetRootElement(), uia) ?? (retries > RETRIES ? null : FindBrowserDocument(retries + 1));
34 | }
35 |
36 | ///
37 | /// This is used only internally to recurse through the elements in the UI tree
38 | /// to find the DOM.
39 | ///
40 | /// The parent element to search from
41 | /// A UIAutomation8 element that serves as a walker factory
42 | /// The browser, or null if it cannot be found
43 | static IUIAutomationElement FindBrowserDocumentRecurser(IUIAutomationElement parent, CUIAutomation8 uia)
44 | {
45 | try
46 | {
47 | var walker = uia.RawViewWalker;
48 | var element = walker.GetFirstChildElement(parent);
49 |
50 | for (element = walker.GetFirstChildElement(parent); element != null; element = walker.GetNextSiblingElement(element))
51 | {
52 | var className = element.CurrentClassName;
53 | var name = element.CurrentName;
54 |
55 | if (name != null && name.Contains("Microsoft Edge"))
56 | {
57 | var result = FindBrowserDocumentRecurser(element, uia);
58 | if (result != null)
59 | {
60 | return result;
61 | }
62 | }
63 | if (className != null && className.Contains("Spartan"))
64 | {
65 | var result = FindBrowserDocumentRecurser(element, uia);
66 | if (result != null)
67 | {
68 | return result;
69 | }
70 | }
71 | if (className != null && className == "TabWindowClass")
72 | {
73 | return element;
74 | }
75 | }
76 | return null;
77 | }
78 | catch
79 | {
80 | return null;
81 | }
82 | }
83 |
84 | ///
85 | /// This searches the UI tree to find an element with the given tag.
86 | ///
87 | /// The browser element to search.
88 | /// The tag to search for
89 | /// An alternative search strategy for elements which are not found by their controltype
90 | /// A list of all control types found on the page, for error reporting
91 | /// The elements found which match the tag given
92 | public static List SearchChildren(
93 | IUIAutomationElement browserElement,
94 | UIAControlType controlType,
95 | Func searchStrategy,
96 | out HashSet foundControlTypes)
97 | {
98 | var uia = new CUIAutomation8();
99 |
100 | var walker = uia.RawViewWalker;
101 | var tosearch = new List>();
102 | var toreturn = new List();
103 | foundControlTypes = new HashSet();
104 |
105 | //We use a 0 here to signify the depth in the BFS search tree. The root element will have depth of 0.
106 | tosearch.Add(new Tuple(browserElement, 0));
107 |
108 | while (tosearch.Any(e => e.Item2 < RECURSIONDEPTH))
109 | {
110 | var current = tosearch.First().Item1;
111 | var currentdepth = tosearch.First().Item2;
112 |
113 | var convertedRole = GetControlTypeFromCode(current.CurrentControlType);
114 | foundControlTypes.Add(convertedRole);
115 |
116 | if (searchStrategy == null ? convertedRole == controlType : searchStrategy(current))
117 | {
118 | toreturn.Add(current);
119 | }
120 | else
121 | {
122 | for (var child = walker.GetFirstChildElement(current); child != null; child = walker.GetNextSiblingElement(child))
123 | {
124 | tosearch.Add(new Tuple(child, currentdepth + 1));
125 | }
126 | }
127 |
128 | tosearch.RemoveAt(0);
129 | }
130 | return toreturn;
131 | }
132 |
133 | ///
134 | /// Find a list of the elements on the page that can be found by
135 | /// tabbing through the UI.
136 | ///
137 | /// The WebDriver wrapper
138 | /// A list of the ids of all tabbable elements
139 | public static List TabbableIds(DriverManager driverManager)
140 | {
141 | const int timeout = 0;
142 | //add a dummy input element at the top of the page so we can start tabbing from there
143 | driverManager.ExecuteScript(
144 | "document.body.innerHTML = \"\" + document.body.innerHTML",
145 | timeout);
146 |
147 | var toreturn = new List();
148 | var id = "";
149 |
150 | for (var i = 1; id != "tabroot"; i++)
151 | {
152 | if (id != "" && !toreturn.Contains(id))
153 | {
154 | toreturn.Add(id);
155 | }
156 |
157 | driverManager.SendTabs("tabroot", i);
158 |
159 | var activeElement = driverManager.ExecuteScript("return document.activeElement", timeout) as IWebElement;
160 | if (activeElement != null) id = activeElement.GetAttribute("id");
161 | }
162 |
163 | //remove the dummy input so we don't influence any other tests
164 | driverManager.ExecuteScript(
165 | "document.body.removeChild(document.getElementById(\"tabroot\"))",
166 | timeout);
167 | return toreturn;
168 | }
169 | }
170 |
171 | ///
172 | /// Some extension methods
173 | ///
174 | public static class Extensions
175 | {
176 | ///
177 | /// Send to send tab keys to an element
178 | ///
179 | /// The driver being extended
180 | /// The element to send the tabs to
181 | /// The number of times to send tab
182 | public static void SendTabs(this DriverManager driver, string element, int count)
183 | {
184 | var tabs = new List();
185 | for(var i = 0; i< count; i++)
186 | {
187 | tabs.Add(WebDriverKey.Tab);
188 | }
189 | driver.SendSpecialKeys(element, tabs);
190 | }
191 |
192 |
193 | ///
194 | /// A wrapper which converts strings with friendly-named special keys to be
195 | /// converted into the appropriate character codes.
196 | ///
197 | /// E.G. "Arrow_left" becomes '\uE012'.toString()
198 | ///
199 | ///
200 | ///
201 | ///
202 | public static void SendSpecialKeys(this DriverManager driver, string elementId, List keys)
203 | {
204 | var stringToSend = new StringBuilder();
205 | foreach (var key in keys)
206 | {
207 | if(key == WebDriverKey.Wait)
208 | {
209 | System.Threading.Thread.Sleep(1000);
210 | driver.SendKeys(elementId, stringToSend.ToString());
211 | stringToSend = new StringBuilder();
212 | }
213 | else
214 | {
215 | stringToSend.Append(GetWebDriverKeyString(key));
216 | }
217 | }
218 | driver.SendKeys(elementId, stringToSend.ToString());
219 | }
220 |
221 | public static void SendSpecialKeys(this DriverManager driver, string elementId, WebDriverKey key)
222 | {
223 | driver.SendSpecialKeys(elementId, new List { key });
224 | }
225 |
226 | ///
227 | /// Get all of the Control Patterns supported by an element
228 | ///
229 | /// The element being extended
230 | /// The ids of the patterns
231 | /// A list of all the patterns supported
232 | public static List GetPatterns(this IUIAutomationElement element, out List ids)
233 | {
234 | int[] inIds;
235 | string[] names;
236 | new CUIAutomation8().PollForPotentialSupportedPatterns(element, out inIds, out names);
237 | ids = inIds.ToList();
238 | return names.ToList();
239 | }
240 |
241 | ///
242 | /// Get all of the Control Patterns supported by an element
243 | ///
244 | /// The element being extended
245 | /// A list of all the patterns supported
246 | public static List GetPatterns(this IUIAutomationElement element)
247 | {
248 | var ids = new List();
249 | return GetPatterns(element, out ids);
250 | }
251 |
252 | ///
253 | /// Get all the properties supported by an element
254 | ///
255 | /// The element being extended
256 | /// A list of all the properties supported
257 | public static List GetProperties(this IUIAutomationElement element)
258 | {
259 | int[] ids;
260 | string[] names;
261 | new CUIAutomation8().PollForPotentialSupportedProperties(element, out ids, out names);
262 | return names.ToList();
263 | }
264 |
265 | ///
266 | /// Get the names of all children (not all descendents)
267 | ///
268 | /// The element being extended
269 | /// A list of all the children's names
270 | public static List GetChildNames(this IUIAutomationElement element, Func searchStrategy = null)
271 | {
272 | var toReturn = new List();
273 | var walker = new CUIAutomation8().RawViewWalker;
274 | for (var child = walker.GetFirstChildElement(element); child != null; child = walker.GetNextSiblingElement(child))
275 | {
276 | if (searchStrategy == null || searchStrategy(child))
277 | {
278 | toReturn.Add(child.CurrentName);
279 | }
280 | }
281 |
282 | return toReturn;
283 | }
284 |
285 | ///
286 | /// Find all descendents of a given element, optionally searching with the given strategy
287 | ///
288 | /// The element whose descendents we need to find
289 | /// The strategy we should use to evaluate children
290 | /// All elements that pass the searchStrategy
291 | public static List GetAllDescendents(this IUIAutomationElement element, Func searchStrategy = null)
292 | {
293 | var toReturn = new List();
294 | var walker = new CUIAutomation8().RawViewWalker;
295 |
296 | var toSearch = new Queue();//BFS
297 | toSearch.Enqueue(element);
298 |
299 | while (toSearch.Any())//beware infinite recursion. If this becomes a problem add a limit
300 | {
301 | var current = toSearch.Dequeue();
302 | for (var child = walker.GetFirstChildElement(current); child != null; child = walker.GetNextSiblingElement(child))
303 | {
304 | toSearch.Enqueue(child);
305 | }
306 |
307 | if (searchStrategy == null || searchStrategy(current))
308 | {
309 | toReturn.Add(current);
310 | }
311 | }
312 |
313 | return toReturn;
314 | }
315 |
316 | ///
317 | /// Parse the mystery object that is returned from WebDriver ExecuteScript calls.
318 | ///
319 | public static double ParseMystery(this object o)
320 | {
321 | try
322 | {
323 | return (int)o;
324 | }
325 | catch { }
326 |
327 | try
328 | {
329 | return (Int64)o;
330 | }
331 | catch { }
332 |
333 | try
334 | {
335 | return (double)o;
336 | }
337 | catch { }
338 |
339 | try
340 | {
341 | return double.Parse((string)o);
342 | }
343 | catch { }
344 |
345 | throw new InvalidCastException("Cannot parse to int, double, or string");
346 | }
347 |
348 | ///
349 | /// Takes a screenshot and saves in the current directory
350 | ///
351 | ///
352 | ///
353 | public static void Screenshot(this DriverManager driver, string name)
354 | {
355 | driver.GetScreenshot().SaveAsFile(Path.Combine(Directory.GetCurrentDirectory(), name + ".png"), ImageFormat.Png);
356 | }
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/EdgeStrategy.cs:
--------------------------------------------------------------------------------
1 | using Interop.UIAutomationCore;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using static Microsoft.Edge.A11y.ElementConverter;
7 |
8 | namespace Microsoft.Edge.A11y
9 | {
10 | ///
11 | /// A strategy for testing Edge Accessibility as scored at
12 | /// http://html5accessibility.com/
13 | ///
14 | internal class EdgeStrategy : TestStrategy
15 | {
16 | public EdgeStrategy(string repositoryPath = "https://cdn.rawgit.com/DHBrett/AT-browser-tests/gh-pages/test-files/", string fileSuffix = "")
17 | {
18 | _driverManager = new DriverManager(TimeSpan.FromSeconds(10));
19 | System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));//Wait for the browser to load before we start searching
20 | _RepositoryPath = repositoryPath;
21 | _FileSuffix = fileSuffix;
22 | }
23 |
24 | ///
25 | /// This handles most of the work of the test cases.
26 | ///
27 | /// N.B. all the test case results are returned in pairs, since we need to be
28 | /// able to give half scores for certain results.
29 | ///
30 | /// An object which stores information about the
31 | /// expected results
32 | ///
33 | ///
34 | internal override IEnumerable TestElement(TestData testData)
35 | {
36 | //Find the browser
37 | var browserElement = EdgeA11yTools.FindBrowserDocument(0);
38 | if (browserElement == null)
39 | {
40 | return Fail(testData.TestName, "Unable to find the browser");
41 | }
42 |
43 | //Find elements using ControlType or the alternate search strategy
44 | HashSet foundControlTypes;
45 | var testElements = EdgeA11yTools.SearchChildren(browserElement, testData.ControlType, testData.SearchStrategy, out foundControlTypes);
46 | if (testElements.Count == 0)
47 | {
48 | return Fail(testData.TestName, testData.SearchStrategy == null ?
49 | "Unable to find the element, found these instead: " + foundControlTypes.Select(ct => ct.ToString()).Aggregate((a, b) => a + ", " + b) :
50 | "Unable to find the element using the alternate search strategy");
51 | }
52 |
53 | var moreInfo = new StringBuilder();
54 |
55 | //If necessary, check localized control type
56 | if (testData.LocalizedControlType != null)
57 | {
58 | foreach (var element in testElements)
59 | {
60 | if (!element.CurrentLocalizedControlType.Equals(testData.LocalizedControlType, StringComparison.OrdinalIgnoreCase))
61 | {
62 | var error = "\nElement did not have the correct localized control type. Expected:" +
63 | testData.LocalizedControlType + " Actual:" + element.CurrentLocalizedControlType;
64 | moreInfo.Append(error);
65 | }
66 | }
67 | }
68 |
69 | //If necessary, check landmark and localized landmark types
70 | if (testData.LandmarkType != UIALandmarkType.Unknown)
71 | {
72 | foreach (var element in testElements)
73 | {
74 | var five = element as IUIAutomationElement5;
75 | var convertedLandmark = GetLandmarkTypeFromCode(five.CurrentLandmarkType);
76 | var localizedLandmark = five.CurrentLocalizedLandmarkType;
77 |
78 | if (convertedLandmark != testData.LandmarkType)
79 | {
80 | var error = "\nElement did not have the correct landmark type. Expected:" +
81 | testData.LandmarkType + " Actual:" + convertedLandmark + "\n";
82 | moreInfo.Append(error);
83 | }
84 |
85 | if (localizedLandmark != testData.LocalizedLandmarkType)
86 | {
87 | var error = "\nElement did not have the correct localized landmark type. Expected:" +
88 | testData.LocalizedLandmarkType + " Actual:" + localizedLandmark + "\n";
89 | moreInfo.Append(error);
90 | }
91 | }
92 | }
93 |
94 | //If necessary, naming and descriptions
95 | //This is done "out of order" since the keyboard checks below invalidate the tree
96 | if (testData.RequiredNames != null || testData.RequiredDescriptions != null)
97 | {
98 | moreInfo.Append(CheckElementNames(testElements,
99 | testData.RequiredNames ?? new List(),
100 | testData.RequiredDescriptions ?? new List()));
101 | }
102 |
103 | //If necessary, check keboard accessibility
104 | var tabbable = EdgeA11yTools.TabbableIds(_driverManager);
105 | if (testData.KeyboardElements != null && testData.KeyboardElements.Count > 0)
106 | {
107 | foreach (var e in testData.KeyboardElements)
108 | {
109 | if (!tabbable.Contains(e))
110 | {
111 | moreInfo.Append("\nCould not access element with id: '" + e + "' by tab");
112 | }
113 | }
114 | }
115 |
116 | try
117 | {
118 | //If necessary, check any additional requirements
119 | if (testData.AdditionalRequirement != null)
120 | {
121 | testElements = EdgeA11yTools.SearchChildren(browserElement, testData.ControlType, testData.SearchStrategy, out foundControlTypes);
122 | var additionalRequirementResult = testData.AdditionalRequirement(testElements, _driverManager, tabbable);
123 | if (additionalRequirementResult.Result != ResultType.Pass)
124 | {
125 | moreInfo.AppendLine(additionalRequirementResult.MoreInfo);
126 | }
127 | }
128 | }
129 | catch (Exception ex)
130 | {
131 | moreInfo.Append("\nCaught exception during test execution, ERROR: " + ex.Message + "\nCallStack:\n" + ex.StackTrace);
132 | }
133 |
134 | var moreInfoString = moreInfo.ToString();
135 | if (moreInfoString != "")
136 | {
137 | return Half(testData.TestName, moreInfoString.Trim());
138 | }
139 |
140 | return Pass(testData.TestName);
141 | }
142 |
143 | ///
144 | /// Check all the elements for correct naming and descriptions
145 | ///
146 | ///
147 | ///
148 | ///
149 | ///
150 | public static string CheckElementNames(List elements, List requiredNames, List requiredDescriptions)
151 | {
152 | var names = elements.ConvertAll(element => element.CurrentName).Where(e => !string.IsNullOrEmpty(e)).ToList();
153 | var descriptions = elements.ConvertAll(element => ((IUIAutomationElement6)element).CurrentFullDescription).Where(e => !string.IsNullOrEmpty(e)).ToList();
154 | var result = "";
155 |
156 | //Check names
157 | var expectedNotFound = requiredNames.Where(rn => !names.Contains(rn)).ToList();//get a list of all required names not found
158 | var foundNotExpected = names.Where(n => !requiredNames.Contains(n)).ToList();//get a list of all found names that weren't required
159 | result +=
160 | expectedNotFound.Any() ? "\n" +
161 | expectedNotFound.Aggregate((a, b) => a + ", " + b) +
162 | (expectedNotFound.Count() > 1 ?
163 | " were expected as names but not found. " :
164 | " was expected as a name but not found. ")
165 | : "";
166 | result +=
167 | foundNotExpected.Any() ? "\n" +
168 | foundNotExpected.Aggregate((a, b) => a + ", " + b) +
169 | (foundNotExpected.Count() > 1 ?
170 | " were found as names but not expected. " :
171 | " was found as a name but not expected. ")
172 | : "";
173 |
174 | //Check descriptions
175 | expectedNotFound = requiredDescriptions.Where(rd => !descriptions.Contains(rd)).ToList();
176 | foundNotExpected = descriptions.Where(d => !requiredDescriptions.Contains(d)).ToList();
177 | result +=
178 | expectedNotFound.Any() ? "\n" +
179 | expectedNotFound.Aggregate((a, b) => a + ", " + b) +
180 | (expectedNotFound.Count() > 1 ?
181 | " were expected as descriptions but not found. " :
182 | " was expected as a description but not found. ")
183 | : "";
184 | result +=
185 | foundNotExpected.Any() ? "\n" +
186 | foundNotExpected.Aggregate((a, b) => a + ", " + b) +
187 | (foundNotExpected.Count() > 1 ?
188 | " were found as descriptions but not expected. " :
189 | " was found as a description but not expected. ")
190 | : "";
191 |
192 | return result;
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/ElementConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Microsoft.Edge.A11y
6 | {
7 | ///
8 | /// A tool to convert element names into codes and back again
9 | ///
10 | public static class ElementConverter
11 | {
12 | private static Dictionary _ControlTypeMapping;
13 | private static Dictionary _PropertyMapping;
14 | private static Dictionary _LandmarkTypeMapping;
15 | private static Dictionary _WebDriverKeyMapping;
16 |
17 | private static void InitControlTypeMapping()
18 | {
19 | var ControlTypeMapping = new Dictionary();
20 | ControlTypeMapping.Add(-1, UIAControlType.Unknown);
21 | ControlTypeMapping.Add(50000, UIAControlType.Button);
22 | ControlTypeMapping.Add(50001, UIAControlType.Calendar);
23 | ControlTypeMapping.Add(50002, UIAControlType.Checkbox);
24 | ControlTypeMapping.Add(50003, UIAControlType.Combobox);
25 | ControlTypeMapping.Add(50004, UIAControlType.Edit);
26 | ControlTypeMapping.Add(50005, UIAControlType.Hyperlink);
27 | ControlTypeMapping.Add(50006, UIAControlType.Image);
28 | ControlTypeMapping.Add(50007, UIAControlType.Listitem);
29 | ControlTypeMapping.Add(50008, UIAControlType.List);
30 | ControlTypeMapping.Add(50009, UIAControlType.Menu);
31 | ControlTypeMapping.Add(50010, UIAControlType.Menubar);
32 | ControlTypeMapping.Add(50011, UIAControlType.Menuitem);
33 | ControlTypeMapping.Add(50012, UIAControlType.Progressbar);
34 | ControlTypeMapping.Add(50013, UIAControlType.Radiobutton);
35 | ControlTypeMapping.Add(50014, UIAControlType.Scrollbar);
36 | ControlTypeMapping.Add(50015, UIAControlType.Slider);
37 | ControlTypeMapping.Add(50016, UIAControlType.Spinner);
38 | ControlTypeMapping.Add(50017, UIAControlType.Statusbar);
39 | ControlTypeMapping.Add(50018, UIAControlType.Tab);
40 | ControlTypeMapping.Add(50019, UIAControlType.Tabitem);
41 | ControlTypeMapping.Add(50020, UIAControlType.Text);
42 | ControlTypeMapping.Add(50021, UIAControlType.Toolbar);
43 | ControlTypeMapping.Add(50022, UIAControlType.Tooltip);
44 | ControlTypeMapping.Add(50023, UIAControlType.Tree);
45 | ControlTypeMapping.Add(50024, UIAControlType.Treeitem);
46 | ControlTypeMapping.Add(50025, UIAControlType.Custom);
47 | ControlTypeMapping.Add(50026, UIAControlType.Group);
48 | ControlTypeMapping.Add(50027, UIAControlType.Thumb);
49 | ControlTypeMapping.Add(50028, UIAControlType.Datagrid);
50 | ControlTypeMapping.Add(50029, UIAControlType.Dataitem);
51 | ControlTypeMapping.Add(50030, UIAControlType.Document);
52 | ControlTypeMapping.Add(50031, UIAControlType.Splitbutton);
53 | ControlTypeMapping.Add(50032, UIAControlType.Window);
54 | ControlTypeMapping.Add(50033, UIAControlType.Pane);
55 | ControlTypeMapping.Add(50034, UIAControlType.Header);
56 | ControlTypeMapping.Add(50035, UIAControlType.Headeritem);
57 | ControlTypeMapping.Add(50036, UIAControlType.Table);
58 | ControlTypeMapping.Add(50037, UIAControlType.Titlebar);
59 | ControlTypeMapping.Add(50038, UIAControlType.Separator);
60 | ControlTypeMapping.Add(50039, UIAControlType.Semanticzoom);
61 | ControlTypeMapping.Add(50040, UIAControlType.Appbar);
62 |
63 | _ControlTypeMapping = ControlTypeMapping;
64 | }
65 |
66 | private static void InitPropertyMapping()
67 | {
68 | var PropertyMapping = new Dictionary();
69 | PropertyMapping.Add(-1, UIAProperty.Unknown);
70 | PropertyMapping.Add(30000, UIAProperty.RuntimeId);
71 | PropertyMapping.Add(30001, UIAProperty.BoundingRectangle);
72 | PropertyMapping.Add(30002, UIAProperty.ProcessId);
73 | PropertyMapping.Add(30003, UIAProperty.ControlType);
74 | PropertyMapping.Add(30004, UIAProperty.LocalizedControlType);
75 | PropertyMapping.Add(30005, UIAProperty.Name);
76 | PropertyMapping.Add(30006, UIAProperty.AcceleratorKey);
77 | PropertyMapping.Add(30007, UIAProperty.AccessKey);
78 | PropertyMapping.Add(30008, UIAProperty.HasKeyboardFocus);
79 | PropertyMapping.Add(30009, UIAProperty.IsKeyboardFocusable);
80 | PropertyMapping.Add(30010, UIAProperty.IsEnabled);
81 | PropertyMapping.Add(30011, UIAProperty.AutomationId);
82 | PropertyMapping.Add(30012, UIAProperty.ClassName);
83 | PropertyMapping.Add(30013, UIAProperty.HelpText);
84 | PropertyMapping.Add(30014, UIAProperty.ClickablePoint);
85 | PropertyMapping.Add(30015, UIAProperty.Culture);
86 | PropertyMapping.Add(30016, UIAProperty.IsControlElement);
87 | PropertyMapping.Add(30017, UIAProperty.IsContentElement);
88 | PropertyMapping.Add(30018, UIAProperty.LabeledBy);
89 | PropertyMapping.Add(30019, UIAProperty.IsPassword);
90 | PropertyMapping.Add(30020, UIAProperty.NativeWindowHandle);
91 | PropertyMapping.Add(30021, UIAProperty.ItemType);
92 | PropertyMapping.Add(30022, UIAProperty.IsOffscreen);
93 | PropertyMapping.Add(30023, UIAProperty.Orientation);
94 | PropertyMapping.Add(30024, UIAProperty.FrameworkId);
95 | PropertyMapping.Add(30025, UIAProperty.IsRequiredForForm);
96 | PropertyMapping.Add(30026, UIAProperty.ItemStatus);
97 | PropertyMapping.Add(30027, UIAProperty.IsDockPatternAvailable);
98 | PropertyMapping.Add(30028, UIAProperty.IsExpandCollapsePatternAvailable);
99 | PropertyMapping.Add(30029, UIAProperty.IsGridItemPatternAvailable);
100 | PropertyMapping.Add(30030, UIAProperty.IsGridPatternAvailable);
101 | PropertyMapping.Add(30031, UIAProperty.IsInvokePatternAvailable);
102 | PropertyMapping.Add(30032, UIAProperty.IsMultipleViewPatternAvailable);
103 | PropertyMapping.Add(30033, UIAProperty.IsRangeValuePatternAvailable);
104 | PropertyMapping.Add(30034, UIAProperty.IsScrollPatternAvailable);
105 | PropertyMapping.Add(30035, UIAProperty.IsScrollItemPatternAvailable);
106 | PropertyMapping.Add(30036, UIAProperty.IsSelectionItemPatternAvailable);
107 | PropertyMapping.Add(30037, UIAProperty.IsSelectionPatternAvailable);
108 | PropertyMapping.Add(30038, UIAProperty.IsTablePatternAvailable);
109 | PropertyMapping.Add(30039, UIAProperty.IsTableItemPatternAvailable);
110 | PropertyMapping.Add(30040, UIAProperty.IsTextPatternAvailable);
111 | PropertyMapping.Add(30041, UIAProperty.IsTogglePatternAvailable);
112 | PropertyMapping.Add(30042, UIAProperty.IsTransformPatternAvailable);
113 | PropertyMapping.Add(30043, UIAProperty.IsValuePatternAvailable);
114 | PropertyMapping.Add(30044, UIAProperty.IsWindowPatternAvailable);
115 | PropertyMapping.Add(30045, UIAProperty.ValueValue);
116 | PropertyMapping.Add(30046, UIAProperty.ValueIsReadOnly);
117 | PropertyMapping.Add(30047, UIAProperty.RangeValueValue);
118 | PropertyMapping.Add(30048, UIAProperty.RangeValueIsReadOnly);
119 | PropertyMapping.Add(30049, UIAProperty.RangeValueMinimum);
120 | PropertyMapping.Add(30050, UIAProperty.RangeValueMaximum);
121 | PropertyMapping.Add(30051, UIAProperty.RangeValueLargeChange);
122 | PropertyMapping.Add(30052, UIAProperty.RangeValueSmallChange);
123 | PropertyMapping.Add(30053, UIAProperty.ScrollHorizontalScrollPercent);
124 | PropertyMapping.Add(30054, UIAProperty.ScrollHorizontalViewSize);
125 | PropertyMapping.Add(30055, UIAProperty.ScrollVerticalScrollPercent);
126 | PropertyMapping.Add(30056, UIAProperty.ScrollVerticalViewSize);
127 | PropertyMapping.Add(30057, UIAProperty.ScrollHorizontallyScrollable);
128 | PropertyMapping.Add(30058, UIAProperty.ScrollVerticallyScrollable);
129 | PropertyMapping.Add(30059, UIAProperty.SelectionSelection);
130 | PropertyMapping.Add(30060, UIAProperty.SelectionCanSelectMultiple);
131 | PropertyMapping.Add(30061, UIAProperty.SelectionIsSelectionRequired);
132 | PropertyMapping.Add(30062, UIAProperty.GridRowCount);
133 | PropertyMapping.Add(30063, UIAProperty.GridColumnCount);
134 | PropertyMapping.Add(30064, UIAProperty.GridItemRow);
135 | PropertyMapping.Add(30065, UIAProperty.GridItemColumn);
136 | PropertyMapping.Add(30066, UIAProperty.GridItemRowSpan);
137 | PropertyMapping.Add(30067, UIAProperty.GridItemColumnSpan);
138 | PropertyMapping.Add(30068, UIAProperty.GridItemContainingGrid);
139 | PropertyMapping.Add(30069, UIAProperty.DockDockPosition);
140 | PropertyMapping.Add(30070, UIAProperty.ExpandCollapseExpandCollapseState);
141 | PropertyMapping.Add(30071, UIAProperty.MultipleViewCurrentView);
142 | PropertyMapping.Add(30072, UIAProperty.MultipleViewSupportedViews);
143 | PropertyMapping.Add(30073, UIAProperty.WindowCanMaximize);
144 | PropertyMapping.Add(30074, UIAProperty.WindowCanMinimize);
145 | PropertyMapping.Add(30075, UIAProperty.WindowWindowVisualState);
146 | PropertyMapping.Add(30076, UIAProperty.WindowWindowInteractionState);
147 | PropertyMapping.Add(30077, UIAProperty.WindowIsModal);
148 | PropertyMapping.Add(30078, UIAProperty.WindowIsTopmost);
149 | PropertyMapping.Add(30079, UIAProperty.SelectionItemIsSelected);
150 | PropertyMapping.Add(30080, UIAProperty.SelectionItemSelectionContainer);
151 | PropertyMapping.Add(30081, UIAProperty.TableRowHeaders);
152 | PropertyMapping.Add(30082, UIAProperty.TableColumnHeaders);
153 | PropertyMapping.Add(30083, UIAProperty.TableRowOrColumnMajor);
154 | PropertyMapping.Add(30084, UIAProperty.TableItemRowHeaderItems);
155 | PropertyMapping.Add(30085, UIAProperty.TableItemColumnHeaderItems);
156 | PropertyMapping.Add(30086, UIAProperty.ToggleToggleState);
157 | PropertyMapping.Add(30087, UIAProperty.TransformCanMove);
158 | PropertyMapping.Add(30088, UIAProperty.TransformCanResize);
159 | PropertyMapping.Add(30089, UIAProperty.TransformCanRotate);
160 | PropertyMapping.Add(30090, UIAProperty.IsLegacyIAccessiblePatternAvailable);
161 | PropertyMapping.Add(30091, UIAProperty.LegacyIAccessibleChildId);
162 | PropertyMapping.Add(30092, UIAProperty.LegacyIAccessibleName);
163 | PropertyMapping.Add(30093, UIAProperty.LegacyIAccessibleValue);
164 | PropertyMapping.Add(30094, UIAProperty.LegacyIAccessibleDescription);
165 | PropertyMapping.Add(30095, UIAProperty.LegacyIAccessibleRole);
166 | PropertyMapping.Add(30096, UIAProperty.LegacyIAccessibleState);
167 | PropertyMapping.Add(30097, UIAProperty.LegacyIAccessibleHelp);
168 | PropertyMapping.Add(30098, UIAProperty.LegacyIAccessibleKeyboardShortcut);
169 | PropertyMapping.Add(30099, UIAProperty.LegacyIAccessibleSelection);
170 | PropertyMapping.Add(30100, UIAProperty.LegacyIAccessibleDefaultAction);
171 | PropertyMapping.Add(30101, UIAProperty.AriaRole);
172 | PropertyMapping.Add(30102, UIAProperty.AriaProperties);
173 | PropertyMapping.Add(30103, UIAProperty.IsDataValidForForm);
174 | PropertyMapping.Add(30104, UIAProperty.ControllerFor);
175 | PropertyMapping.Add(30105, UIAProperty.DescribedBy);
176 | PropertyMapping.Add(30106, UIAProperty.FlowsTo);
177 | PropertyMapping.Add(30107, UIAProperty.ProviderDescription);
178 | PropertyMapping.Add(30108, UIAProperty.IsItemContainerPatternAvailable);
179 | PropertyMapping.Add(30109, UIAProperty.IsVirtualizedItemPatternAvailable);
180 | PropertyMapping.Add(30110, UIAProperty.IsSynchronizedInputPatternAvailable);
181 |
182 | _PropertyMapping = PropertyMapping;
183 | }
184 |
185 | private static void InitLandmarkTypeMapping()
186 | {
187 | var LandmarkTypeMapping = new Dictionary();
188 | LandmarkTypeMapping.Add(-1, UIALandmarkType.Unknown);
189 | LandmarkTypeMapping.Add(80000, UIALandmarkType.Custom);
190 | LandmarkTypeMapping.Add(80001, UIALandmarkType.Form);
191 | LandmarkTypeMapping.Add(80002, UIALandmarkType.Main);
192 | LandmarkTypeMapping.Add(80003, UIALandmarkType.Navigation);
193 | LandmarkTypeMapping.Add(80004, UIALandmarkType.Search);
194 | _LandmarkTypeMapping = LandmarkTypeMapping;
195 | }
196 |
197 | private static void InitWebDriverKeyMapping()
198 | {
199 | var WebDriverKeyMapping = new Dictionary();
200 |
201 | WebDriverKeyMapping.Add(WebDriverKey.Wait, null);
202 | WebDriverKeyMapping.Add(WebDriverKey.Null, '\uE000'.ToString());
203 | WebDriverKeyMapping.Add(WebDriverKey.Cancel, '\uE001'.ToString());
204 | WebDriverKeyMapping.Add(WebDriverKey.Help, '\uE002'.ToString());
205 | WebDriverKeyMapping.Add(WebDriverKey.Back_space, '\uE003'.ToString());
206 | WebDriverKeyMapping.Add(WebDriverKey.Tab, '\uE004'.ToString());
207 | WebDriverKeyMapping.Add(WebDriverKey.Clear, '\uE005'.ToString());
208 | WebDriverKeyMapping.Add(WebDriverKey.Return, '\uE006'.ToString());
209 | WebDriverKeyMapping.Add(WebDriverKey.Enter, '\uE007'.ToString());
210 | WebDriverKeyMapping.Add(WebDriverKey.Shift, '\uE008'.ToString());
211 | WebDriverKeyMapping.Add(WebDriverKey.Control, '\uE009'.ToString());
212 | WebDriverKeyMapping.Add(WebDriverKey.Alt, '\uE00A'.ToString());
213 | WebDriverKeyMapping.Add(WebDriverKey.Pause, '\uE00B'.ToString());
214 | WebDriverKeyMapping.Add(WebDriverKey.Escape, '\uE00C'.ToString());
215 | WebDriverKeyMapping.Add(WebDriverKey.Space, '\uE00D'.ToString());
216 | WebDriverKeyMapping.Add(WebDriverKey.Page_up, '\uE00E'.ToString());
217 | WebDriverKeyMapping.Add(WebDriverKey.Page_down, '\uE00F'.ToString());
218 | WebDriverKeyMapping.Add(WebDriverKey.End, '\uE010'.ToString());
219 | WebDriverKeyMapping.Add(WebDriverKey.Home, '\uE011'.ToString());
220 | WebDriverKeyMapping.Add(WebDriverKey.Arrow_left, '\uE012'.ToString());
221 | WebDriverKeyMapping.Add(WebDriverKey.Arrow_up, '\uE013'.ToString());
222 | WebDriverKeyMapping.Add(WebDriverKey.Arrow_right, '\uE014'.ToString());
223 | WebDriverKeyMapping.Add(WebDriverKey.Arrow_down, '\uE015'.ToString());
224 | WebDriverKeyMapping.Add(WebDriverKey.Insert, '\uE016'.ToString());
225 | WebDriverKeyMapping.Add(WebDriverKey.Delete, '\uE017'.ToString());
226 | WebDriverKeyMapping.Add(WebDriverKey.Semicolon, '\uE018'.ToString());
227 | WebDriverKeyMapping.Add(WebDriverKey.Equals, '\uE019'.ToString());
228 | WebDriverKeyMapping.Add(WebDriverKey.Numpad0, '\uE01A'.ToString());
229 | WebDriverKeyMapping.Add(WebDriverKey.Numpad1, '\uE01B'.ToString());
230 | WebDriverKeyMapping.Add(WebDriverKey.Numpad2, '\uE01C'.ToString());
231 | WebDriverKeyMapping.Add(WebDriverKey.Numpad3, '\uE01D'.ToString());
232 | WebDriverKeyMapping.Add(WebDriverKey.Numpad4, '\uE01E'.ToString());
233 | WebDriverKeyMapping.Add(WebDriverKey.Numpad5, '\uE01F'.ToString());
234 | WebDriverKeyMapping.Add(WebDriverKey.Numpad6, '\uE020'.ToString());
235 | WebDriverKeyMapping.Add(WebDriverKey.Numpad7, '\uE021'.ToString());
236 | WebDriverKeyMapping.Add(WebDriverKey.Numpad8, '\uE022'.ToString());
237 | WebDriverKeyMapping.Add(WebDriverKey.Numpad9, '\uE023'.ToString());
238 | WebDriverKeyMapping.Add(WebDriverKey.Multiply, '\uE024'.ToString());
239 | WebDriverKeyMapping.Add(WebDriverKey.Add, '\uE025'.ToString());
240 | WebDriverKeyMapping.Add(WebDriverKey.Separator, '\uE026'.ToString());
241 | WebDriverKeyMapping.Add(WebDriverKey.Subtract, '\uE027'.ToString());
242 | WebDriverKeyMapping.Add(WebDriverKey.Decimal, '\uE028'.ToString());
243 | WebDriverKeyMapping.Add(WebDriverKey.Divide, '\uE029'.ToString());
244 | WebDriverKeyMapping.Add(WebDriverKey.F1, '\uE031'.ToString());
245 | WebDriverKeyMapping.Add(WebDriverKey.F2, '\uE032'.ToString());
246 | WebDriverKeyMapping.Add(WebDriverKey.F3, '\uE033'.ToString());
247 | WebDriverKeyMapping.Add(WebDriverKey.F4, '\uE034'.ToString());
248 | WebDriverKeyMapping.Add(WebDriverKey.F5, '\uE035'.ToString());
249 | WebDriverKeyMapping.Add(WebDriverKey.F6, '\uE036'.ToString());
250 | WebDriverKeyMapping.Add(WebDriverKey.F7, '\uE037'.ToString());
251 | WebDriverKeyMapping.Add(WebDriverKey.F8, '\uE038'.ToString());
252 | WebDriverKeyMapping.Add(WebDriverKey.F9, '\uE039'.ToString());
253 | WebDriverKeyMapping.Add(WebDriverKey.F10, '\uE03A'.ToString());
254 | WebDriverKeyMapping.Add(WebDriverKey.F11, '\uE03B'.ToString());
255 | WebDriverKeyMapping.Add(WebDriverKey.F12, '\uE03C'.ToString());
256 | WebDriverKeyMapping.Add(WebDriverKey.Meta, '\uE03D'.ToString());
257 | WebDriverKeyMapping.Add(WebDriverKey.Command, '\uE03D'.ToString());
258 | WebDriverKeyMapping.Add(WebDriverKey.Zenkaku_hankaku, '\uE040'.ToString());
259 |
260 | _WebDriverKeyMapping = WebDriverKeyMapping;
261 | }
262 |
263 | ///
264 | /// Convert a code into an element name
265 | ///
266 | ///
267 | ///
268 | public static UIAControlType GetControlTypeFromCode(int code)
269 | {
270 | if (_ControlTypeMapping == null)
271 | {
272 | InitControlTypeMapping();
273 | }
274 | return _ControlTypeMapping.ContainsKey(code) ? _ControlTypeMapping[code] : UIAControlType.Unknown;
275 | }
276 |
277 | ///
278 | /// Convert a code into a property
279 | ///
280 | ///
281 | ///
282 | public static UIAProperty GetPropertyFromCode(int code)
283 | {
284 | if (_PropertyMapping == null)
285 | {
286 | InitPropertyMapping();
287 | }
288 | return _PropertyMapping.ContainsKey(code) ? _PropertyMapping[code] : UIAProperty.Unknown;
289 | }
290 |
291 | public static int GetPropertyCode(UIAProperty property)
292 | {
293 | if (_PropertyMapping == null)
294 | {
295 | InitPropertyMapping();
296 | }
297 | //will throw if given an invalid code
298 | return _PropertyMapping.Keys.First(k => _PropertyMapping[k] == property);
299 | }
300 |
301 | ///
302 | /// Convert a code into a landmark
303 | ///
304 | ///
305 | ///
306 | public static UIALandmarkType GetLandmarkTypeFromCode(int code)
307 | {
308 | if (_LandmarkTypeMapping == null)
309 | {
310 | InitLandmarkTypeMapping();
311 | }
312 | return _LandmarkTypeMapping.ContainsKey(code) ? _LandmarkTypeMapping[code] : UIALandmarkType.Unknown;
313 | }
314 |
315 | ///
316 | /// Convert a WebDriverKey into its string representation
317 | ///
318 | ///
319 | ///
320 | public static string GetWebDriverKeyString(WebDriverKey key)
321 | {
322 | if (_WebDriverKeyMapping == null)
323 | {
324 | InitWebDriverKeyMapping();
325 | }
326 | return _WebDriverKeyMapping[key];
327 | }
328 |
329 | public enum UIAControlType
330 | {
331 | Unknown,
332 | Button,
333 | Calendar,
334 | Checkbox,
335 | Combobox,
336 | Edit,
337 | Hyperlink,
338 | Image,
339 | Listitem,
340 | List,
341 | Menu,
342 | Menubar,
343 | Menuitem,
344 | Progressbar,
345 | Radiobutton,
346 | Scrollbar,
347 | Slider,
348 | Spinner,
349 | Statusbar,
350 | Tab,
351 | Tabitem,
352 | Text,
353 | Toolbar,
354 | Tooltip,
355 | Tree,
356 | Treeitem,
357 | Custom,
358 | Group,
359 | Thumb,
360 | Datagrid,
361 | Dataitem,
362 | Document,
363 | Splitbutton,
364 | Window,
365 | Pane,
366 | Header,
367 | Headeritem,
368 | Table,
369 | Titlebar,
370 | Separator,
371 | Semanticzoom,
372 | Appbar,
373 | }
374 |
375 | public enum UIAProperty
376 | {
377 | Unknown,
378 | RuntimeId,
379 | BoundingRectangle,
380 | ProcessId,
381 | ControlType,
382 | LocalizedControlType,
383 | Name,
384 | AcceleratorKey,
385 | AccessKey,
386 | HasKeyboardFocus,
387 | IsKeyboardFocusable,
388 | IsEnabled,
389 | AutomationId,
390 | ClassName,
391 | HelpText,
392 | ClickablePoint,
393 | Culture,
394 | IsControlElement,
395 | IsContentElement,
396 | LabeledBy,
397 | IsPassword,
398 | NativeWindowHandle,
399 | ItemType,
400 | IsOffscreen,
401 | Orientation,
402 | FrameworkId,
403 | IsRequiredForForm,
404 | ItemStatus,
405 | IsDockPatternAvailable,
406 | IsExpandCollapsePatternAvailable,
407 | IsGridItemPatternAvailable,
408 | IsGridPatternAvailable,
409 | IsInvokePatternAvailable,
410 | IsMultipleViewPatternAvailable,
411 | IsRangeValuePatternAvailable,
412 | IsScrollPatternAvailable,
413 | IsScrollItemPatternAvailable,
414 | IsSelectionItemPatternAvailable,
415 | IsSelectionPatternAvailable,
416 | IsTablePatternAvailable,
417 | IsTableItemPatternAvailable,
418 | IsTextPatternAvailable,
419 | IsTogglePatternAvailable,
420 | IsTransformPatternAvailable,
421 | IsValuePatternAvailable,
422 | IsWindowPatternAvailable,
423 | ValueValue,
424 | ValueIsReadOnly,
425 | RangeValueValue,
426 | RangeValueIsReadOnly,
427 | RangeValueMinimum,
428 | RangeValueMaximum,
429 | RangeValueLargeChange,
430 | RangeValueSmallChange,
431 | ScrollHorizontalScrollPercent,
432 | ScrollHorizontalViewSize,
433 | ScrollVerticalScrollPercent,
434 | ScrollVerticalViewSize,
435 | ScrollHorizontallyScrollable,
436 | ScrollVerticallyScrollable,
437 | SelectionSelection,
438 | SelectionCanSelectMultiple,
439 | SelectionIsSelectionRequired,
440 | GridRowCount,
441 | GridColumnCount,
442 | GridItemRow,
443 | GridItemColumn,
444 | GridItemRowSpan,
445 | GridItemColumnSpan,
446 | GridItemContainingGrid,
447 | DockDockPosition,
448 | ExpandCollapseExpandCollapseState,
449 | MultipleViewCurrentView,
450 | MultipleViewSupportedViews,
451 | WindowCanMaximize,
452 | WindowCanMinimize,
453 | WindowWindowVisualState,
454 | WindowWindowInteractionState,
455 | WindowIsModal,
456 | WindowIsTopmost,
457 | SelectionItemIsSelected,
458 | SelectionItemSelectionContainer,
459 | TableRowHeaders,
460 | TableColumnHeaders,
461 | TableRowOrColumnMajor,
462 | TableItemRowHeaderItems,
463 | TableItemColumnHeaderItems,
464 | ToggleToggleState,
465 | TransformCanMove,
466 | TransformCanResize,
467 | TransformCanRotate,
468 | IsLegacyIAccessiblePatternAvailable,
469 | LegacyIAccessibleChildId,
470 | LegacyIAccessibleName,
471 | LegacyIAccessibleValue,
472 | LegacyIAccessibleDescription,
473 | LegacyIAccessibleRole,
474 | LegacyIAccessibleState,
475 | LegacyIAccessibleHelp,
476 | LegacyIAccessibleKeyboardShortcut,
477 | LegacyIAccessibleSelection,
478 | LegacyIAccessibleDefaultAction,
479 | AriaRole,
480 | AriaProperties,
481 | IsDataValidForForm,
482 | ControllerFor,
483 | DescribedBy,
484 | FlowsTo,
485 | ProviderDescription,
486 | IsItemContainerPatternAvailable,
487 | IsVirtualizedItemPatternAvailable,
488 | IsSynchronizedInputPatternAvailable,
489 | }
490 |
491 | public enum UIALandmarkType
492 | {
493 | Unknown,
494 | Custom,
495 | Form,
496 | Main,
497 | Navigation,
498 | Search
499 | }
500 |
501 | public enum WebDriverKey
502 | {
503 | Null,
504 | Cancel,
505 | Help,
506 | Back_space,
507 | Tab,
508 | Clear,
509 | Return,
510 | Enter,
511 | Shift,
512 | Control,
513 | Alt,
514 | Pause,
515 | Escape,
516 | Space,
517 | Page_up,
518 | Page_down,
519 | End,
520 | Home,
521 | Arrow_left,
522 | Arrow_up,
523 | Arrow_right,
524 | Arrow_down,
525 | Insert,
526 | Delete,
527 | Semicolon,
528 | Equals,
529 | Numpad0,
530 | Numpad1,
531 | Numpad2,
532 | Numpad3,
533 | Numpad4,
534 | Numpad5,
535 | Numpad6,
536 | Numpad7,
537 | Numpad8,
538 | Numpad9,
539 | Multiply,
540 | Add,
541 | Separator,
542 | Subtract,
543 | Decimal,
544 | Divide,
545 | F1,
546 | F2,
547 | F3,
548 | F4,
549 | F5,
550 | F6,
551 | F7,
552 | F8,
553 | F9,
554 | F10,
555 | F11,
556 | F12,
557 | Meta,
558 | Command,
559 | Zenkaku_hankaku,
560 | Wait
561 | }
562 | }
563 | }
564 |
--------------------------------------------------------------------------------
/Interop.UIAutomationCore.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftEdge/A11y/926dcaedcb5c9f45f0d4e10ae09942882483c74e/Interop.UIAutomationCore.dll
--------------------------------------------------------------------------------
/Javascript.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Microsoft.Edge.A11y
4 | {
5 | ///
6 | /// Used to store JavaScript that is passed to the page for testing
7 | ///
8 | static class Javascript
9 | {
10 | ///
11 | /// Modernizr feature detection for the track element
12 | ///
13 | public static string Track = "/*! modernizr 3.3.1 (Custom Build) | MIT * * http://modernizr.com/download/?-texttrackapi_track-setclasses !*/ !function(e,n,t){function a(e,n){return typeof e===n}function s(){var e,n,t,s,o,i,c;for(var f in l)if(l.hasOwnProperty(f)){if(e=[],n=l[f],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t
16 | /// Clear focus, so that we can start over in the tab order
17 | ///
18 | public static Action ClearFocus = (driverManager, timeout) => driverManager.ExecuteScript("document.activeElement.blur()", timeout);
19 |
20 | ///
21 | /// Scroll the element into view (for screenshot)
22 | ///
23 | public static Action ScrollIntoView = (driverManager, timeout) => driverManager.ExecuteScript("document.activeElement.scrollIntoView()", timeout);
24 |
25 | ///
26 | /// Remove hidden attribute from all buttons on the page
27 | ///
28 | public static string RemoveHidden = "var ps = document.getElementsByTagName('input'); for(i = 0; i < ps.length; i++){ ps[i].hidden = false}";
29 |
30 | ///
31 | /// Change aria-hidden attribute to false for the second hidden element
32 | ///
33 | public static string RemoveAriaHidden = "document.getElementById('input2').setAttribute('aria-hidden', false)";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Microsoft.Win32;
6 |
7 | namespace Microsoft.Edge.A11y
8 | {
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | var testName = args.FirstOrDefault();
14 |
15 | TestStrategy a11yStrategy = new EdgeStrategy(fileSuffix: ".html");
16 |
17 | var results = TestData.alltests.Value.Where(td =>
18 | (testName == null || td.TestName == testName)) //Either no test name was provided or the test names match
19 | .ToList().ConvertAll(td => a11yStrategy.Execute(td)) //Execute each of the tests
20 | .Where(r => r.Any()) //Only keep the ones that were executed
21 | .ToList().ConvertAll(r => //Convert results from internal form (Pass/Pass, Pass/Fail, Fail/Fail) to external (Pass, Half, Fail)
22 | {
23 | var first = r.ElementAt(0);
24 | var second = r.ElementAt(1);
25 | second.Result = second.Result == ResultType.Fail && first.Result == ResultType.Pass ? ResultType.Half : second.Result;
26 | second.Name = second.Name.Replace("-2", "");
27 | return second;
28 | });
29 |
30 | //output results to the console: failures, then halves, then passes
31 | results.OrderBy(r => r.Result == ResultType.Pass).ThenBy(r => r.Result == ResultType.Half).ToList().ForEach(r => Console.WriteLine(r));
32 |
33 | if (results.Any())
34 | {
35 | var score = results.ConvertAll(r =>
36 | {
37 | switch (r.Result)
38 | {
39 | case ResultType.Fail:
40 | return 0;
41 | case ResultType.Half:
42 | return .5;
43 | case ResultType.Pass:
44 | return 1;
45 | default:
46 | throw new InvalidDataException();
47 | }
48 | }).Average() * 100;
49 |
50 | Console.WriteLine("Edge Score: " + score);
51 |
52 | ResultsToCSV(results, score);
53 | }
54 | else
55 | {
56 | Console.WriteLine("No tests matched the name " + args[0]);
57 | }
58 |
59 | a11yStrategy.Close();
60 | }
61 |
62 | public static void ResultsToCSV(List results, double score)
63 | {
64 | //Get the file
65 | var filePath = Path.Combine(DriverManager.ProjectRootFolder, "scores.csv");
66 |
67 | //If this is the first time, write the header line with the test names
68 | if (!File.Exists(filePath))
69 | {
70 | var headerLine = "buildNumber,buildIteration,buildArchitecture,buildBranch,buildDate,score,time," +
71 | results.Select(r => r.Name + "," + r.Name + "-details").Aggregate((s1, s2) => s1 + "," + s2) + "\n";
72 | File.WriteAllText(filePath, headerLine);
73 | }
74 |
75 | var build = Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "BuildLabEx", null);
76 | if (build == null || build as string == null)
77 | {
78 | throw new Exception("Unable to get build string");
79 | }
80 |
81 | var time = DateTime.Now.ToString("yyyyMMdd-HHmm");
82 |
83 | //Write the results
84 | var writer = File.AppendText(filePath);
85 | var resultline = (build as string).Replace('.', ',') + "," + score + "," + time + "," +
86 | results.Select(r => r.Result.ToString() + "," + (r.MoreInfo != null ? r.MoreInfo.Replace('\n', '\t') : ""))
87 | .Aggregate((s1, s2) => s1 + "," + s2);
88 | writer.WriteLine(resultline);
89 |
90 | writer.Flush();
91 | writer.Close();
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("A11y")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("A11y")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("18ca2a08-637b-48cd-81b6-6b7b08e5d441")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A11y Automation Test Suite
2 | An automated implementation of [html5accessibility.com](http://html5accessibility.com/)
3 | for Microsoft Edge.
4 |
5 | ## Running
6 | If you have Visual Studio, simply open A11y.sln and run.
7 |
8 | Otherwise you can build and run in one step by calling run.ps1 within PowerShell.
9 |
10 | ### Building and running manually
11 | It's also possible to run manually without Visual Studio or PowerShell.
12 |
13 | First navigate to the project's root directory and install nuget:
14 | ``` powershell "(new-object net.webclient).DownloadFile('https://nuget.org/nuget.exe', 'nuget.exe')" ```
15 |
16 | Then restore the packages:
17 | ``` nuget.exe restore A11y.sln ```
18 |
19 | Finally find MSBuild.exe, which comes pre-installed with the .NET framework. It will be
20 | in the C:\Windows\Microsoft.NET\Framework folder.
21 | For example: C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe.
22 | **Make sure to use the version installed on your computer.**
23 |
24 | Use MSBuild to compile the solution: ```
25 | C:\Windows\Microsoft.NET\Framework\\MSBuild.exe A11y.sln ```
26 |
27 | Then just run the compiled program: ``` bin\Debug\Microsoft.Edge.A11y.exe ```
28 |
29 | ## Scores and reporting
30 | For a full explanation of how scores are calculated, see
31 | [html5accessibility.com](http://html5accessibility.com/).
32 |
33 | After the tests have run, the results are printed to the console and saved in the root
34 | directory of the project with the name "scores.csv".
35 |
36 | ## Testing your site
37 | It's possible to use A11y to automate testing of your site as well. A sample project is
38 | included on the site_testing branch.
39 |
40 | ### Test files
41 | The sample test page is included in this repo on the site_testing branch. It's possible
42 | (and better) to have your sites in another location. Just change the constructor call to
43 | to the TestStrategy class passing in the base URL where your test files are located.
44 |
45 | ### Pass and failure conditions
46 | The TestData.cs file contains the logic of the tests and an explanation of the built-in
47 | tests. To add your own tests, add a TestData object for each test you want to run.
48 |
49 | By default, elements are found by their control type, but you can pass in a custom method
50 | of searching by adding a searchStrategy parameter.
51 |
52 | In addition to the default tests, you can use the additionalRequirement paramter to
53 | specify any other requirement that you want to verify. If you find yourself using the
54 | same additionalRequirement for many of your tests, you may want to add another parameter
55 | to the TestData constructor to simplify testing that requirement.
56 |
57 | ## Contributing
58 | We want your feedback and your help! If you have any suggestions, file an issue and we
59 | can figure out how to get your needs met.
60 |
61 | If you'd like to submit code changes, the best thing to do is to file an issue first so
62 | we can talk about whether the change would fit with the direction and purpose of the
63 | project. Even if your changes don't fit with the general purpose of A11y, we'd love to
64 | see you fork the project to do new things with it.
65 |
66 | ## Legal
67 |
68 | You will need to complete a [Contributor License Agreement (CLA)](https://cla.microsoft.com/) before your pull request can be accepted.
69 | This agreement testifies that you are granting us permission to use the source code you are submitting, and that
70 | this work is being submitted under appropriate license that we can use it.
71 | The process is very simple as it just hooks into your Github account. Once we have received the signed CLA, we'll review the request.
72 | You will only need to do this once.
73 |
74 | ##Code of Conduct
75 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
76 |
77 |
--------------------------------------------------------------------------------
/TestCaseResult.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Microsoft.Edge.A11y
4 | {
5 | public enum ResultType
6 | {
7 | Fail = 0,
8 | Pass = 1,
9 | Unknown = 2,
10 | Half = 3
11 | }
12 |
13 | public class TestCaseResult
14 | {
15 | public ResultType Result { get; set; }
16 | public string Name { get; set; }
17 | public string MoreInfo { get; set; }
18 | public string Browser { get; set; }
19 |
20 | public override string ToString()
21 | {
22 | return Name + ":\n\t" + Result + (MoreInfo != null ? " (" + MoreInfo + ")" : "");
23 | }
24 |
25 | public string ToCSVString()
26 | {
27 | return Name + "," + Result + (MoreInfo != null ? "," + MoreInfo : "");
28 | }
29 | }
30 |
31 |
32 | public class TestCaseResultExt : TestCaseResult
33 | {
34 | StringBuilder _MoreInfo = new StringBuilder();
35 |
36 | new public string MoreInfo
37 | {
38 | get
39 | {
40 | return _MoreInfo.ToString();
41 | }
42 | }
43 |
44 | public TestCaseResultExt()
45 | {
46 | Result = ResultType.Pass;
47 | }
48 |
49 | public void AddInfo(string info)
50 | {
51 | _MoreInfo.Append(info);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/TestData.cs:
--------------------------------------------------------------------------------
1 | using Interop.UIAutomationCore;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading;
6 | using static Microsoft.Edge.A11y.ElementConverter;
7 |
8 | namespace Microsoft.Edge.A11y
9 | {
10 | ///
11 | /// This is where the logic of the tests is stored
12 | ///
13 | class TestData
14 | {
15 | public const double epsilon = .001;
16 |
17 | ///
18 | /// The name of the test, which corresponds to the name of the html element
19 | ///
20 | private string _TestName;
21 | public string TestName
22 | {
23 | get
24 | {
25 | return _TestName;
26 | }
27 | }
28 |
29 | ///
30 | /// The name of the UIA control type we will use to search for the element
31 | ///
32 | private UIAControlType _ControlType;
33 | public UIAControlType ControlType
34 | {
35 | get
36 | {
37 | return _ControlType;
38 | }
39 | }
40 |
41 | ///
42 | /// The name of the UIA localized control type, which will be part of the test
43 | /// case if it is not null
44 | ///
45 | private string _LocalizedControlType;
46 | public string LocalizedControlType
47 | {
48 | get
49 | {
50 | return _LocalizedControlType;
51 | }
52 | }
53 |
54 | ///
55 | /// The name of the UIA landmark type, which will be part of the test
56 | /// case if it is not null
57 | ///
58 | private UIALandmarkType _LandmarkType;
59 | public UIALandmarkType LandmarkType
60 | {
61 | get
62 | {
63 | return _LandmarkType;
64 | }
65 | }
66 |
67 | ///
68 | /// The name of the UIA localized landmark type, which will be part of the test
69 | /// case if it is not null
70 | ///
71 | private string _LocalizedLandmarkType;
72 | public string LocalizedLandmarkType
73 | {
74 | get
75 | {
76 | return _LocalizedLandmarkType;
77 | }
78 | }
79 |
80 | ///
81 | /// A list of ids for all the elements that should be keyboard accessible (via tab)
82 | ///
83 | private List _KeyboardElements;
84 | public List KeyboardElements
85 | {
86 | get
87 | {
88 | return _KeyboardElements;
89 | }
90 | }
91 |
92 | ///
93 | /// If not null, this func will be used to test elements to see if they should be
94 | /// tested (instead of matching _ControlType).
95 | ///
96 | private Func _SearchStrategy;
97 | public Func SearchStrategy
98 | {
99 | get
100 | {
101 | return _SearchStrategy;
102 | }
103 | }
104 |
105 | ///
106 | /// A list of expected values which will be compared to the accessible names of
107 | /// the found elements.
108 | ///
109 | private List _requiredNames;
110 | public List RequiredNames
111 | {
112 | get
113 | {
114 | return _requiredNames;
115 | }
116 | }
117 |
118 | ///
119 | /// Same as above, but for accessible descriptions.
120 | ///
121 | private List _RequiredDescriptions;
122 | public List RequiredDescriptions
123 | {
124 | get
125 | {
126 | return _RequiredDescriptions;
127 | }
128 | }
129 |
130 | ///
131 | /// A func that allows extending the tests for specific elements.
132 | ///
133 | private Func, DriverManager, List, TestCaseResultExt> _AdditionalRequirement;
134 | public Func, DriverManager, List, TestCaseResultExt> AdditionalRequirement
135 | {
136 | get
137 | {
138 | return _AdditionalRequirement;
139 | }
140 | }
141 |
142 | ///
143 | /// Simple Ctor
144 | ///
145 | ///
146 | ///
147 | ///
148 | ///
149 | ///
150 | ///
151 | ///
152 | ///
153 | ///
154 | ///
155 | public TestData(string testName,
156 | UIAControlType controlType,
157 | string localizedControlType = null,
158 | UIALandmarkType landmarkType = UIALandmarkType.Unknown,
159 | string localizedLandmarkType = null,
160 | List keyboardElements = null,
161 | Func searchStrategy = null,
162 | List requiredNames = null,
163 | List requiredDescriptions = null,
164 | Func, DriverManager, List, TestCaseResultExt> additionalRequirement = null)
165 | {
166 | _TestName = testName;
167 | _ControlType = controlType;
168 | _LocalizedControlType = localizedControlType;
169 | _LandmarkType = landmarkType;
170 | _LocalizedLandmarkType = localizedLandmarkType;
171 | _KeyboardElements = keyboardElements;
172 | _SearchStrategy = searchStrategy;
173 | _requiredNames = requiredNames;
174 | _RequiredDescriptions = requiredDescriptions;
175 | _AdditionalRequirement = additionalRequirement;
176 | }
177 |
178 | //All the tests to run
179 | public static Lazy> alltests = new Lazy>(AllTests);
180 |
181 | ///
182 | /// Get the TestData for the given test page
183 | ///
184 | /// The name of the file being tested
185 | /// TestData for the given test page, or null if it couldn't be found
186 | public static TestData DataFromName(string testName)
187 | {
188 | return alltests.Value.FirstOrDefault(t => t._TestName == testName);
189 | }
190 |
191 | ///
192 | /// Singleton initializer
193 | ///
194 | ///
195 | static List AllTests()
196 | {
197 | const int timeout = 0;
198 | var uia = new CUIAutomation8();
199 | var walker = uia.RawViewWalker;
200 |
201 | return new List{
202 | new TestData("article", UIAControlType.Group, "article",
203 | requiredNames:
204 | new List{
205 | "aria-label attribute 3",
206 | "h1 referenced by aria-labelledby4",
207 | "title attribute 5",
208 | "aria-label attribute 7"},
209 | requiredDescriptions:
210 | new List{
211 | "h1 referenced by aria-describedby6",
212 | "title attribute 7"
213 | }),
214 | new TestData("aside", UIAControlType.Group, "aside", UIALandmarkType.Custom, "complementary",
215 | requiredNames:
216 | new List{
217 | "aria-label attribute 3",
218 | "h1 referenced by aria-labelledby4",
219 | "title attribute 5",
220 | "aria-label attribute 7"},
221 | requiredDescriptions:
222 | new List{
223 | "h1 referenced by aria-describedby6",
224 | "title attribute 7"
225 | }),
226 | new TestData("audio", UIAControlType.Group, "audio",
227 | additionalRequirement: ((elements, driver, ids) => {
228 | var result = new TestCaseResultExt();
229 |
230 | CheckChildNames(new List {
231 | "Play",
232 | "Time elapsed",
233 | "Seek",
234 | "Time remaining",
235 | "Mute",
236 | "Volume"})(elements, driver, ids, result);
237 |
238 | CheckAudioKeyboardInteractions(elements, driver, ids, result);
239 | return result;
240 | }
241 | )),
242 | new TestData("canvas", UIAControlType.Image,
243 | additionalRequirement: ((elements, driver, ids) => {
244 | var result = new TestCaseResultExt();
245 | var subdomElements = new List();
246 |
247 | foreach (var e in elements)
248 | {
249 | subdomElements.AddRange(e.GetAllDescendents((d) =>
250 | {
251 | var convertedRole =
252 | GetControlTypeFromCode(d.CurrentControlType);
253 | return (convertedRole == UIAControlType.Button || convertedRole == UIAControlType.Text);
254 | }));
255 | }
256 | if(subdomElements.Count() != 3)
257 | {
258 | result.Result = ResultType.Half;
259 | result.AddInfo("Unable to find subdom elements");
260 | }
261 |
262 | var featureDetectionScript = @"canvas = document.getElementById('myCanvas');
263 | isSupported = !!(canvas.getContext && canvas.getContext('2d'));
264 | isSupported = isSupported && !!(canvas.getContext('2d').drawFocusIfNeeded);
265 | return isSupported;";
266 |
267 | if(!(bool)driver.ExecuteScript(featureDetectionScript, timeout))
268 | {
269 | result.Result = ResultType.Half;
270 | result.AddInfo("\nFailed feature detection");
271 | }
272 |
273 | return result;
274 | })),
275 | new TestData("datalist", UIAControlType.Combobox, keyboardElements: new List { "input1" },
276 | additionalRequirement: ((elements, driver, ids) => {
277 | Func datalistValue = (id) => (string)driver.ExecuteScript("return document.getElementById('" + id + "').value", 0);
278 | var result = new TestCaseResultExt();
279 |
280 | foreach(var element in elements)
281 | {
282 | var elementFive = (IUIAutomationElement5)element;
283 | List patternIds;
284 | var names = elementFive.GetPatterns(out patternIds);
285 |
286 | if (!names.Contains("SelectionPattern"))
287 | {
288 | result.Result = ResultType.Half;
289 | result.AddInfo("\nElement did not support SelectionPattern");
290 | }
291 | else {
292 | var selectionPattern = (IUIAutomationSelectionPattern)elementFive.GetCurrentPattern(
293 | patternIds[names.IndexOf("SelectionPattern")]);
294 |
295 | if(selectionPattern.CurrentCanSelectMultiple == 1)
296 | {
297 | result.Result = ResultType.Half;
298 | result.AddInfo("\nCanSelectMultiple set to true");
299 | }
300 | }
301 | }
302 | var previousControllerForElements = new HashSet();
303 |
304 | //keyboard a11y
305 | foreach (var id in ids.Take(1))
306 | {
307 | var initial = datalistValue(id);
308 | driver.SendSpecialKeys(id, WebDriverKey.Arrow_down);
309 |
310 | var controllerForElements = elements.Where(e => e.CurrentControllerFor != null && e.CurrentControllerFor.Length > 0).ToList().Select(element => elements.IndexOf(element));
311 | if(controllerForElements.All(element => previousControllerForElements.Contains(element))){
312 | result.Result = ResultType.Half;
313 | result.AddInfo("Element controller for not set for id: " + id);
314 | }
315 |
316 | previousControllerForElements.Add(controllerForElements.First(element => !previousControllerForElements.Contains(element)));
317 |
318 | driver.SendSpecialKeys(id, WebDriverKey.Enter);
319 | if (datalistValue(id) != "Item value 1")
320 | {
321 | result.Result = ResultType.Half;
322 | result.AddInfo("Unable to set the datalist with keyboard for element with id: " + id);
323 | return result;
324 | }
325 | }
326 |
327 | return result;
328 | })),
329 | new TestData("figure", UIAControlType.Group, "figure",
330 | requiredNames:
331 | new List{
332 | "aria-label attribute 2",
333 | "p referenced by aria-labelledby3",
334 | "title attribute 4",
335 | "Figcaption element 5",
336 | "Figcaption element 7"},
337 | requiredDescriptions:
338 | new List{
339 | "p referenced by aria-describedby6",
340 | "title attribute 7"
341 | }),
342 | new TestData("figure-figcaption", UIAControlType.Text,//Control type is ignored
343 | searchStrategy: element => true, //Verify this element via text range
344 | additionalRequirement: ((elements, driver, ids) =>
345 | {
346 | var result = new TestCaseResultExt();
347 | var logoText = "HTML5 logo 1";
348 |
349 | //there will be only one, since element is the pane in this case
350 | foreach(var element in elements) {
351 | var five = (IUIAutomationElement5)
352 | walker.GetFirstChildElement(
353 | walker.GetFirstChildElement(elements[0]));//only have the pane element, take its grandchild
354 | List patternIds;
355 | var names = five.GetPatterns(out patternIds);
356 |
357 | if (!names.Contains("TextPattern"))
358 | {
359 | result.Result = ResultType.Half;
360 | result.AddInfo("\nPane did not support TextPattern, unable to search");
361 | return result;
362 | }
363 |
364 | var textPattern = (IUIAutomationTextPattern)five.GetCurrentPattern(
365 | patternIds[names.IndexOf("TextPattern")]);
366 |
367 | var documentRange = textPattern.DocumentRange;
368 |
369 | var foundText = documentRange.GetText(1000);
370 | if (!foundText.Contains(logoText))
371 | {
372 | result.Result = ResultType.Half;
373 | result.AddInfo("\nText not found on page");
374 | }
375 |
376 | var foundControlTypes = new HashSet();
377 | var figure = EdgeA11yTools.SearchChildren(element, UIAControlType.Group, null, out foundControlTypes);
378 |
379 | var childRange = textPattern.RangeFromChild(figure[0]);
380 |
381 | var childRangeText = childRange.GetText(1000).Trim();
382 |
383 | if(childRangeText != logoText)
384 | {
385 | result.Result = ResultType.Half;
386 | result.AddInfo(string.Format("\nUnable to find correct text range. Found '{0}' instead", childRangeText));
387 | }
388 | }
389 |
390 | return result;
391 | })),
392 | new TestData("footer", UIAControlType.Group,
393 | searchStrategy: element =>
394 | GetControlTypeFromCode(element.CurrentControlType) == UIAControlType.Group
395 | && element.CurrentLocalizedControlType != "article",
396 | requiredNames:
397 | new List{
398 | "aria-label attribute 3",
399 | "small referenced by aria-labelledby4",
400 | "title attribute 5",
401 | "aria-label attribute 7"},
402 | requiredDescriptions:
403 | new List{
404 | "small referenced by aria-describedby6",
405 | "title attribute 7"
406 | },
407 | additionalRequirement: (elements, driver, ids) => {
408 | var result = new TestCaseResultExt();
409 |
410 | if (elements.Count() != 7)
411 | {
412 | result.Result = ResultType.Half;
413 | result.AddInfo("\nFound " + elements.Count() + " elements, expected 7.");
414 | }
415 |
416 | var convertedLandmarks = 0;
417 | var localizedLandmarks = 0;
418 | //same for landmark and localizedlandmark
419 | foreach (var element in elements)
420 | {
421 | var five = element as IUIAutomationElement5;
422 | var convertedLandmark = GetLandmarkTypeFromCode(five.CurrentLandmarkType);
423 | var localizedLandmark = five.CurrentLocalizedLandmarkType;
424 | if (convertedLandmark == UIALandmarkType.Custom)
425 | {
426 | convertedLandmarks++;
427 | }
428 | if (localizedLandmark == "content information")
429 | {
430 | localizedLandmarks++;
431 | }
432 | }
433 | if (convertedLandmarks != 1)
434 | {
435 | result.Result = ResultType.Half;
436 | result.AddInfo("\nFound " + convertedLandmarks + " elements with landmark type Custom, expected 1");
437 | }
438 |
439 | if (localizedLandmarks != 1)
440 | {
441 | result.Result = ResultType.Half;
442 | result.AddInfo("\nFound " + localizedLandmarks + " elements with localized landmark type content information, expected 1");
443 | }
444 |
445 | return result;
446 | }),
447 | new TestData("header", UIAControlType.Group,
448 | searchStrategy: element =>
449 | GetControlTypeFromCode(element.CurrentControlType) == UIAControlType.Group
450 | && element.CurrentLocalizedControlType != "article",
451 | requiredNames:
452 | new List{
453 | "aria-label attribute 3",
454 | "h1 referenced by aria-labelledby4",
455 | "title attribute 5",
456 | "aria-label attribute 7"},
457 | requiredDescriptions:
458 | new List{
459 | "h1 referenced by aria-describedby6",
460 | "title attribute 7"},
461 | additionalRequirement: (elements, driver, ids) => {
462 | var result = new TestCaseResultExt();
463 |
464 | if (elements.Count() != 7)
465 | {
466 | result.Result = ResultType.Half;
467 | result.AddInfo("\nFound " + elements.Count() + " elements, expected 7.");
468 | }
469 |
470 | var convertedLandmarks = 0;
471 | var localizedLandmarks = 0;
472 | var landmarkCode = 0;
473 | //same for landmark and localizedlandmark
474 | foreach (var element in elements)
475 | {
476 | var five = element as IUIAutomationElement5;
477 | var convertedLandmark = GetLandmarkTypeFromCode(five.CurrentLandmarkType);
478 | landmarkCode = five.CurrentLandmarkType;
479 | var localizedLandmark = five.CurrentLocalizedLandmarkType;
480 | if (convertedLandmark == UIALandmarkType.Custom)
481 | {
482 | convertedLandmarks++;
483 | }
484 | if (localizedLandmark == "banner")
485 | {
486 | localizedLandmarks++;
487 | }
488 | }
489 | if (convertedLandmarks != 1)
490 | {
491 | result.Result = ResultType.Half;
492 | result.AddInfo("\nFound " + convertedLandmarks + " elements with landmark type Custom, expected 1: " + landmarkCode);
493 | }
494 |
495 | if (localizedLandmarks != 1)
496 | {
497 | result.Result = ResultType.Half;
498 | result.AddInfo("\nFound " + localizedLandmarks + " elements with localized landmark type banner, expected 1");
499 | }
500 |
501 | return result;
502 | }),
503 | new TestData("input-color", UIAControlType.Button, "color picker",
504 | additionalRequirement: (elements, driver, ids) => {
505 | var result = new TestCaseResultExt();
506 |
507 | var previousControllerForElements = new HashSet();
508 | foreach(var id in ids.Take(1))
509 | {
510 | Func CheckColorValue = () => (string) driver.ExecuteScript("return document.getElementById('"+ id + "').value", timeout);
511 | Action ChangeColorValue = (value) => driver.ExecuteScript("document.getElementById('"+ id + "').value = '" + value + "'", timeout);
512 | Func ActiveElement = () => (string)driver.ExecuteScript("return document.activeElement.id", 0);
513 |
514 | var initial = CheckColorValue();
515 | driver.SendSpecialKeys(id, new List { WebDriverKey.Enter, WebDriverKey.Escape, WebDriverKey.Enter, WebDriverKey.Enter });
516 |
517 | //open dialog to check controllerfor
518 | driver.SendSpecialKeys(id, WebDriverKey.Enter);
519 | var controllerForElements = elements.Where(e => e.CurrentControllerFor != null && e.CurrentControllerFor.Length > 0).ToList().Select(element => elements.IndexOf(element));
520 | if(controllerForElements.All(element => previousControllerForElements.Contains(element))){
521 | result.Result = ResultType.Half;
522 | result.AddInfo("\nElement controller for not set for id: " + id);
523 | }
524 | else
525 | {
526 | //the element that corresponds to this id
527 | var thisElement = elements[controllerForElements.First(element => !previousControllerForElements.Contains(element))];
528 | if(thisElement.CurrentControllerFor.Length > 1){
529 | throw new Exception("\nMore than one ControllerFor present, test assumption failed");
530 | }
531 | var thisDialog = thisElement.CurrentControllerFor.GetElement(0);
532 | var descendents = thisDialog.GetAllDescendents();
533 |
534 | //sliders
535 | var sliders = descendents.Where(d => GetControlTypeFromCode(d.CurrentControlType) == UIAControlType.Slider);
536 | if(sliders.Count() != 3){
537 | result.Result = ResultType.Half;
538 | result.AddInfo("\nDialog did not have three slider elements");
539 | }
540 | else if (!sliders.All(s => s.GetPatterns().Contains("RangeValuePattern")))
541 | {
542 | result.Result = ResultType.Half;
543 | result.AddInfo("\nDialog's sliders did not implement RangeValuePattern");
544 | }
545 |
546 | //buttons
547 | if (descendents.Count(d => GetControlTypeFromCode(d.CurrentControlType) == UIAControlType.Button) != 2)
548 | {
549 | result.Result = ResultType.Half;
550 | result.AddInfo("\nDialog did not have two button elements");
551 | }
552 |
553 | //color well
554 | var outputs = descendents.Where(d => GetControlTypeFromCode(d.CurrentControlType) == UIAControlType.Group && d.CurrentLocalizedControlType == "output");
555 | if (outputs.Count() > 1)
556 | {
557 | throw new Exception("Test assumption failed: expected color dialog to have at most one output");
558 | }
559 | else if (outputs.Count() == 0)
560 | {
561 | result.Result = ResultType.Half;
562 | result.AddInfo("\nCould not find output in color dialog");
563 | }
564 | else if (outputs.Count() == 1)
565 | {
566 | var output = outputs.First();
567 | if (output.CurrentName == null || output.CurrentName == "")
568 | {
569 | result.Result = ResultType.Half;
570 | result.AddInfo("\nColor dialog output did not have name set");
571 | }
572 | else
573 | {
574 | var initialName = output.CurrentName;
575 |
576 | driver.SendSpecialKeys(id, new List { WebDriverKey.Tab,
577 | WebDriverKey.Tab,
578 | WebDriverKey.Arrow_right,
579 | WebDriverKey.Arrow_right });
580 |
581 | if (output.CurrentName == initialName)
582 | {
583 | result.Result = ResultType.Half;
584 | result.AddInfo("\nColor dialog output did not change name when color changed: " + output.CurrentName);
585 | }
586 |
587 | ChangeColorValue("#000000");//Ensure that the color is clear before continuing
588 | }
589 | }
590 | }
591 |
592 | //open with enter, close with escape
593 | driver.SendSpecialKeys(id, new List { WebDriverKey.Escape,
594 | WebDriverKey.Enter,
595 | WebDriverKey.Tab,
596 | WebDriverKey.Arrow_right,
597 | WebDriverKey.Arrow_right,
598 | WebDriverKey.Escape });
599 | if (CheckColorValue() != initial)
600 | {
601 | result.Result = ResultType.Half;
602 | result.AddInfo("\nUnable to cancel with escape");
603 | }
604 |
605 | //open with enter, close with enter
606 | driver.SendSpecialKeys(id, new List {
607 | WebDriverKey.Escape,
608 | WebDriverKey.Enter,
609 | WebDriverKey.Tab,
610 | WebDriverKey.Tab,
611 | WebDriverKey.Arrow_right,
612 | WebDriverKey.Arrow_right,
613 | WebDriverKey.Enter });
614 | if (CheckColorValue() == initial)
615 | {
616 | result.Result = ResultType.Half;
617 | result.AddInfo("\nUnable to change value with arrow keys and submit with enter");
618 | }
619 |
620 | //open with space, close with enter
621 | initial = CheckColorValue();
622 | driver.SendSpecialKeys(id, new List {
623 | WebDriverKey.Escape,
624 | WebDriverKey.Space,
625 | WebDriverKey.Tab,
626 | WebDriverKey.Tab,
627 | WebDriverKey.Arrow_right,
628 | WebDriverKey.Arrow_right,
629 | WebDriverKey.Enter });
630 | if (initial == CheckColorValue())
631 | {
632 | result.Result = ResultType.Half;
633 | result.AddInfo("\nUnable to open dialog with space");
634 | }
635 |
636 | initial = CheckColorValue();
637 |
638 | driver.SendSpecialKeys(id, new List {
639 | WebDriverKey.Enter,
640 | WebDriverKey.Tab,
641 | WebDriverKey.Tab,
642 | WebDriverKey.Tab });
643 | if (ActiveElement() != id)
644 | {
645 | result.Result = ResultType.Half;
646 | result.AddInfo("\nUnable to reach accept/dismiss buttons via tab");
647 | }
648 | else//only try to use the buttons if they're there
649 | {
650 | //**Dismiss button**
651 | //Open the dialog, change hue, tab to cancel button, activate it with space,
652 | //check that tabbing moves to the previous button (on the page not the dialog)
653 | driver.SendSpecialKeys(id, new List { WebDriverKey.Escape,
654 | WebDriverKey.Enter,
655 | WebDriverKey.Arrow_right,
656 | WebDriverKey.Arrow_right,
657 | WebDriverKey.Tab,
658 | WebDriverKey.Tab,
659 | WebDriverKey.Tab,
660 | WebDriverKey.Tab,
661 | WebDriverKey.Space,
662 | WebDriverKey.Shift,
663 | WebDriverKey.Tab,
664 | WebDriverKey.Shift });
665 | if (initial != CheckColorValue() || ActiveElement() == id)
666 | {
667 | result.Result = ResultType.Half;
668 | result.AddInfo("\nUnable to cancel with dismiss button via space");
669 | }
670 |
671 | //do the same as above, but activate the button with enter this time
672 | driver.SendSpecialKeys(id, new List { WebDriverKey.Escape,
673 | WebDriverKey.Enter,
674 | WebDriverKey.Arrow_right,
675 | WebDriverKey.Arrow_right,
676 | WebDriverKey.Tab,
677 | WebDriverKey.Tab,
678 | WebDriverKey.Tab,
679 | WebDriverKey.Tab,
680 | WebDriverKey.Enter,
681 | WebDriverKey.Shift,
682 | WebDriverKey.Tab,
683 | WebDriverKey.Shift });
684 | if (initial != CheckColorValue() || ActiveElement() == id)
685 | {
686 | result.Result = ResultType.Half;
687 | result.AddInfo("\nUnable to cancel with dismiss button via enter");
688 | }
689 |
690 |
691 | //**Accept button**
692 | initial = CheckColorValue();
693 |
694 | //Open the dialog, tab to hue, change hue, tab to accept button, activate it with space,
695 | //send tab (since the dialog should be closed, this will transfer focus to the next
696 | //input-color button)
697 | driver.SendSpecialKeys(id, new List { WebDriverKey.Escape,
698 | WebDriverKey.Enter,
699 | WebDriverKey.Tab,
700 | WebDriverKey.Tab,
701 | WebDriverKey.Arrow_right,
702 | WebDriverKey.Arrow_right,
703 | WebDriverKey.Tab,
704 | WebDriverKey.Space,
705 | WebDriverKey.Tab });
706 | if (initial == CheckColorValue() || ActiveElement() == id)
707 | {
708 | result.Result = ResultType.Half;
709 | result.AddInfo("\nUnable to accept with accept button via space");
710 | }
711 |
712 | initial = CheckColorValue();//the value hopefully changed above, but just to be safe
713 |
714 | //Open the dialog, tab to hue, change hue, tab to accept button, activate it with enter
715 | //We don't have to worry about why the dialog closed here (button or global enter)
716 | driver.SendSpecialKeys(id, new List { WebDriverKey.Escape,
717 | WebDriverKey.Enter,
718 | WebDriverKey.Tab,
719 | WebDriverKey.Tab,
720 | WebDriverKey.Arrow_right,
721 | WebDriverKey.Arrow_right,
722 | WebDriverKey.Tab,
723 | WebDriverKey.Enter,
724 | WebDriverKey.Tab });
725 | if (initial == CheckColorValue() || ActiveElement() == id)
726 | {
727 | result.Result = ResultType.Half;
728 | result.AddInfo("\nUnable to accept with accept button via enter");
729 | }
730 | }
731 | }
732 |
733 | return result;
734 | }),
735 | new TestData("input-date", UIAControlType.Edit,
736 | keyboardElements: new List { "input1", "input2" },
737 | requiredNames:
738 | new List{
739 | "aria-label attribute2",
740 | "p referenced by aria-labelledby3",
741 | "label wrapping input 4",
742 | "title attribute 5",
743 | "label referenced by for/id attributes 7"},
744 | requiredDescriptions:
745 | new List{
746 | "p referenced by aria-describedby6",
747 | "title attribute 7" },
748 | additionalRequirement:
749 | (element, driver, ids) =>
750 | {
751 | var result = new TestCaseResultExt();
752 | CheckCalendar(3)(element, driver, ids, result);
753 | return result;
754 | }),
755 | new TestData("input-datetime-local", UIAControlType.Edit,
756 | requiredNames:
757 | new List{
758 | "aria-label attribute2",
759 | "p referenced by aria-labelledby3",
760 | "label wrapping input 4",
761 | "title attribute 5",
762 | "label referenced by for/id attributes 7"},
763 | requiredDescriptions:
764 | new List{
765 | "p referenced by aria-describedby6",
766 | "title attribute 7" },
767 | additionalRequirement:
768 | (element, driver, ids) =>
769 | {
770 | var result = new TestCaseResultExt();
771 | CheckDatetimeLocal()(element, driver, ids, result);
772 | return result;
773 | }),
774 | new TestData("input-email", UIAControlType.Edit, "email",
775 | keyboardElements: new List { "input1", "input2" },
776 | requiredNames:
777 | new List{
778 | "aria-label attribute2",
779 | "p referenced by aria-labelledby3",
780 | "label wrapping input 4",
781 | "title attribute 5",
782 | "label referenced by for/id attributes 7"},
783 | requiredDescriptions:
784 | new List{
785 | "p referenced by aria-describedby6",
786 | "title attribute 7" },
787 | additionalRequirement: (elements, driver, ids) => {
788 | var result = new TestCaseResultExt();
789 | CheckValidation()(elements, driver, ids, result);
790 | CheckClearButton()(elements, driver, ids, result);
791 | return result;
792 | }),
793 | new TestData("input-month", UIAControlType.Edit, keyboardElements: new List { "input1", "input2" },
794 | requiredNames:
795 | new List{
796 | "aria-label attribute2",
797 | "p referenced by aria-labelledby3",
798 | "label wrapping input 4",
799 | "title attribute 5",
800 | "label referenced by for/id attributes 7"},
801 | requiredDescriptions:
802 | new List{
803 | "p referenced by aria-describedby6",
804 | "title attribute 7" },
805 | additionalRequirement:
806 | (elements, driver, ids) =>
807 | {
808 | var result = new TestCaseResultExt();
809 | CheckCalendar(2)(elements, driver, ids, result);
810 | return result;
811 | }),
812 | new TestData("input-number", UIAControlType.Spinner, "number",
813 | keyboardElements: new List { "input1", "input2" },
814 | requiredNames:
815 | new List{
816 | "aria-label attribute2",
817 | "p referenced by aria-labelledby3",
818 | "label wrapping input 4",
819 | "title attribute 5",
820 | "label referenced by for/id attributes 7"},
821 | requiredDescriptions:
822 | new List{
823 | "p referenced by aria-describedby6",
824 | "title attribute 7" },
825 | additionalRequirement:
826 | (elements, driver, ids) =>
827 | {
828 | var result = new TestCaseResultExt();
829 | CheckValidation()(elements, driver, ids, result);
830 | return result;
831 | }),
832 | new TestData("input-range", UIAControlType.Slider, keyboardElements: new List { "input1", "input2" },
833 | requiredNames:
834 | new List{
835 | "aria-label attribute 2",
836 | "p referenced by aria-labelledby3",
837 | "label wrapping input 4",
838 | "title attribute 5",
839 | "label referenced by for/id attributes 7"},
840 | requiredDescriptions:
841 | new List{
842 | "p referenced by aria-describedby6",
843 | "title attribute 7"},
844 | additionalRequirement: (elements, driver, ids) => {
845 | var result = new TestCaseResultExt();
846 |
847 | //keyboard interaction
848 | foreach(var id in ids.Take(1)){
849 | Func RangeValue = () => (int) Int32.Parse((string) driver.ExecuteScript("return document.getElementById('" + id + "').value", 0));
850 |
851 | var initial = RangeValue();
852 | driver.SendSpecialKeys(id, WebDriverKey.Arrow_up);
853 | if (initial == RangeValue())
854 | {
855 | result.Result = ResultType.Half;
856 | result.AddInfo("\nUnable to increase range with arrow up");
857 | continue;
858 | }
859 | driver.SendSpecialKeys(id, WebDriverKey.Arrow_down);
860 | if (initial != RangeValue())
861 | {
862 | result.Result = ResultType.Half;
863 | result.AddInfo("\nUnable to decrease range with arrow down");
864 | continue;
865 | }
866 |
867 | driver.SendSpecialKeys(id, WebDriverKey.Arrow_right);
868 | if (initial >= RangeValue())
869 | {
870 | result.Result = ResultType.Half;
871 | result.AddInfo("\nUnable to increase range with arrow right");
872 | continue;
873 | }
874 | driver.SendSpecialKeys(id, WebDriverKey.Arrow_left);
875 | if (initial != RangeValue())
876 | {
877 | result.Result = ResultType.Half;
878 | result.AddInfo("\nUnable to decrease range with arrow left");
879 | continue;
880 | }
881 | }
882 |
883 | //rangevalue pattern
884 | foreach(var element in elements){
885 | var rangeValuePattern = "RangeValuePattern";
886 | if (!element.GetPatterns().Contains(rangeValuePattern)) {
887 | result.Result = ResultType.Half;
888 | result.AddInfo("\nElement did not implement the RangeValuePattern");
889 | }
890 | }
891 | return result;
892 | }),
893 | new TestData("input-search", UIAControlType.Edit, "search", keyboardElements: new List { "input1", "input2" },
894 | requiredNames:
895 | new List{
896 | "aria-label attribute 2",
897 | "p referenced by aria-labelledby3",
898 | "label wrapping input 4",
899 | "title attribute 5",
900 | "label referenced by for/id attributes 7"},
901 | requiredDescriptions:
902 | new List{
903 | "p referenced by aria-describedby6",
904 | "title attribute 7"},
905 | additionalRequirement:
906 | (elements, driver, ids) => {
907 | var result = new TestCaseResultExt();
908 | CheckClearButton()(elements, driver, ids, result);
909 | return result;
910 | }),
911 | new TestData("input-tel", UIAControlType.Edit, "telephone", keyboardElements: new List { "input1", "input2" },
912 | requiredNames:
913 | new List{
914 | "aria-label attribute 2",
915 | "p referenced by aria-labelledby3",
916 | "label wrapping input 4",
917 | "title attribute 5",
918 | "label referenced by for/id attributes 7"},
919 | requiredDescriptions:
920 | new List{
921 | "p referenced by aria-describedby6",
922 | "title attribute 7"},
923 | additionalRequirement:
924 | (elements, driver, ids) => {
925 | var result = new TestCaseResultExt();
926 | CheckClearButton()(elements, driver, ids, result);
927 | return result;
928 | }),
929 | new TestData("input-time", UIAControlType.Edit, keyboardElements: new List { "input1", "input2" },
930 | requiredNames:
931 | new List{
932 | "aria-label attribute 2",
933 | "p referenced by aria-labelledby3",
934 | "label wrapping input 4",
935 | "title attribute 5",
936 | "label referenced by for/id attributes 7"},
937 | requiredDescriptions:
938 | new List{
939 | "p referenced by aria-describedby6",
940 | "title attribute 7" },
941 | additionalRequirement:
942 | (elements, driver, ids) => {
943 | var result = new TestCaseResultExt();
944 | CheckCalendar(3, 2)(elements, driver, ids, result);
945 | return result;
946 | }),
947 | new TestData("input-url", UIAControlType.Edit, "url",
948 | keyboardElements: new List { "input1", "input2" },
949 | additionalRequirement:
950 | (elements, driver, ids) => {
951 | var result = new TestCaseResultExt();
952 | CheckValidation()(elements, driver, ids, result);
953 | CheckClearButton()(elements, driver, ids, result);
954 | return result;
955 | }),
956 | new TestData("input-week", UIAControlType.Edit, keyboardElements: new List { "input1", "input2" },
957 | requiredNames:
958 | new List{
959 | "aria-label attribute 2",
960 | "p referenced by aria-labelledby3",
961 | "label wrapping input 4",
962 | "title attribute 5",
963 | "label referenced by for/id attributes 7"},
964 | requiredDescriptions:
965 | new List{
966 | "p referenced by aria-describedby6",
967 | "title attribute 7" },
968 | additionalRequirement:
969 | (elements, driver, ids) => {
970 | var result = new TestCaseResultExt();
971 | CheckCalendar(2)(elements, driver, ids, result);
972 | return result;
973 | }),
974 | new TestData("main", UIAControlType.Group, "main", UIALandmarkType.Main, "main",
975 | requiredNames:
976 | new List{
977 | "title attribute 1",
978 | "aria-label attribute 2",
979 | "h1 referenced by aria-labelledby3",
980 | "title attribute 4",
981 | "aria-label attribute 6"
982 | },
983 | requiredDescriptions:
984 | new List{
985 | "h1 referenced by aria-describedby5",
986 | "title attribute 6"}),
987 | new TestData("mark", UIAControlType.Text,
988 | searchStrategy: element =>
989 | GetControlTypeFromCode(element.CurrentControlType) == UIAControlType.Text
990 | && element.CurrentLocalizedControlType == "mark",
991 | requiredNames:
992 | new List{
993 | "aria-label attribute2",
994 | "Element referenced by aria-labelledby attribute3",
995 | "title attribute 4",
996 | "aria-label attribute 6"
997 | },
998 | requiredDescriptions:
999 | new List{
1000 | "Element referenced by aria-describedby attribute5",
1001 | "title attribute 6"}),
1002 | new TestData("meter", UIAControlType.Progressbar, "meter",
1003 | requiredNames:
1004 | new List{
1005 | "aria-label attribute 2",
1006 | "p referenced by aria-labelledby3",
1007 | "label wrapping meter 4",
1008 | "title attribute 5",
1009 | "label referenced by for/id attributes 7"},
1010 | requiredDescriptions:
1011 | new List{
1012 | "p referenced by aria-describedby6",
1013 | "title attribute 7" },
1014 | additionalRequirement:
1015 | ((elements, driver, ids) => {
1016 | var result = new TestCaseResultExt();
1017 | //readonly
1018 | if(!elements.All(element => element.GetProperties().Any(p => p.Contains("IsReadOnly")))){
1019 | result.Result = ResultType.Half;
1020 | result.AddInfo("Not all elements were read only");
1021 | }
1022 |
1023 | //rangevalue
1024 | foreach (var element in elements)
1025 | {
1026 | var patternName = "RangeValuePattern";
1027 |
1028 | var patterned = GetPattern(patternName, element);
1029 | if(patterned == null)
1030 | {
1031 | result.Result = ResultType.Half;
1032 | result.AddInfo("\nElement did not support " + patternName);
1033 | }
1034 | else
1035 | {
1036 | if (patterned.CurrentMaximum - 100 > epsilon)
1037 | {
1038 | result.Result = ResultType.Half;
1039 | result.AddInfo("\nElement did not have the correct max");
1040 | }
1041 | if (patterned.CurrentMinimum - 0 > epsilon)
1042 | {
1043 | result.Result = ResultType.Half;
1044 | result.AddInfo("\nElement did not have the correct min");
1045 | }
1046 | var value = 83.5;//All the meters are set to this
1047 | if (patterned.CurrentValue - value > epsilon)
1048 | {
1049 | result.Result = ResultType.Half;
1050 | result.AddInfo("\nElement did not have the correct value");
1051 | }
1052 | }
1053 | }
1054 |
1055 | //value
1056 | foreach (var element in elements)
1057 | {
1058 | var patterned = GetPattern("ValuePattern", element);
1059 | if (patterned == null)
1060 | {
1061 | result.Result = ResultType.Half;
1062 | result.AddInfo("\nElement did not support ValuePattern");
1063 | }
1064 | else
1065 | {
1066 | if (patterned.CurrentValue == null || patterned.CurrentValue != "Good")
1067 | {
1068 | result.Result = ResultType.Half;
1069 | result.AddInfo("\nElement did not have value set");
1070 | }
1071 | }
1072 | }
1073 |
1074 | return result;
1075 | }),
1076 | searchStrategy: (element => element.GetPatterns().Contains("RangeValuePattern"))),//NB the ControlType is not used for searching this element
1077 | new TestData("nav", UIAControlType.Group, "navigation", UIALandmarkType.Navigation, "navigation",
1078 | requiredNames:
1079 | new List{
1080 | "aria-label attribute 2",
1081 | "h1 referenced by aria-labelledby3",
1082 | "title attribute 4",
1083 | "aria-label attribute 6"},
1084 | requiredDescriptions:
1085 | new List{
1086 | "h1 referenced by aria-describedby5",
1087 | "title attribute 6"}),
1088 | new TestData("output", UIAControlType.Group, "output",
1089 | requiredNames:
1090 | new List{
1091 | "aria-label attribute 2",
1092 | "p referenced by aria-labelledby3",
1093 | "label wrapping output 410",
1094 | "title attribute 5",
1095 | "label referenced by for/id attributes 7" },
1096 | requiredDescriptions:
1097 | new List{
1098 | "p referenced by aria-describedby6",
1099 | "title attribute 7" },
1100 | additionalRequirement: ((elements, driver, ids) => {
1101 | var result = new TestCaseResultExt();
1102 |
1103 | if (!elements.All(element => ((IUIAutomationElement5)element).CurrentLiveSetting == LiveSetting.Polite)){
1104 | result.Result = ResultType.Half;
1105 | result.AddInfo("\nElement did not have LiveSetting = Polite");
1106 | }
1107 | var controllerForLengths = elements.Select(element => element.CurrentControllerFor != null ? element.CurrentControllerFor.Length : 0);
1108 | if (controllerForLengths.Count(cfl => cfl > 0) != 1)
1109 | {
1110 | result.Result = ResultType.Half;
1111 | result.AddInfo("\nExpected 1 element with ControllerFor set. Found " + controllerForLengths.Count(cfl => cfl > 0));
1112 | }
1113 | return result;
1114 | })),
1115 | new TestData("progress", UIAControlType.Progressbar,
1116 | requiredNames:
1117 | new List{
1118 | "aria-label attribute 2",
1119 | "p referenced by aria-labelledby3",
1120 | "label wrapping output 4",
1121 | "title attribute 5",
1122 | "label referenced by for/id attributes 7" },
1123 | requiredDescriptions:
1124 | new List{
1125 | "p referenced by aria-describedby6",
1126 | "title attribute 7" },
1127 | additionalRequirement: (elements, driver, ids) => {
1128 | var result = new TestCaseResultExt();
1129 |
1130 | //rangevalue
1131 | foreach (var element in elements)
1132 | {
1133 | var patternName = "RangeValuePattern";
1134 |
1135 | var patterned = GetPattern(patternName, element);
1136 | if(patterned == null)
1137 | {
1138 | result.Result = ResultType.Half;
1139 | result.AddInfo("\nElement did not support " + patternName);
1140 | }
1141 | else
1142 | {
1143 | if (patterned.CurrentMaximum - 100 > epsilon)
1144 | {
1145 | result.Result = ResultType.Half;
1146 | result.AddInfo("\nElement did not have the correct max");
1147 | }
1148 | if (patterned.CurrentMinimum - 0 > epsilon)
1149 | {
1150 | result.Result = ResultType.Half;
1151 | result.AddInfo("\nElement did not have the correct min");
1152 | }
1153 | var value = 22;//All the progress bars are set to this
1154 | if (patterned.CurrentValue - value > epsilon)
1155 | {
1156 | result.Result = ResultType.Half;
1157 | result.AddInfo("\nElement did not have the correct value");
1158 | }
1159 | }
1160 | }
1161 |
1162 | return result;
1163 | }),
1164 | new TestData("section", UIAControlType.Group, "section", UIALandmarkType.Custom, "region",
1165 | requiredNames:
1166 | new List{
1167 | "aria-label attribute 3",
1168 | "h1 referenced by aria-labelledby4",
1169 | "title attribute 5",
1170 | "aria-label attribute 7"},
1171 | requiredDescriptions:
1172 | new List{
1173 | "h1 referenced by aria-describedby6",
1174 | "title attribute 7" }),
1175 | new TestData("time", UIAControlType.Text,
1176 | searchStrategy: element =>
1177 | GetControlTypeFromCode(element.CurrentControlType) == UIAControlType.Text
1178 | && element.CurrentLocalizedControlType == "time",
1179 | requiredNames:
1180 | new List{
1181 | "aria-label attribute2",
1182 | "Element referenced by aria-labelledby attribute 3",
1183 | "title attribute 4",
1184 | "aria-label attribute 6"
1185 | },
1186 | requiredDescriptions:
1187 | new List{
1188 | "2015-10-01",
1189 | "2015-10-02",
1190 | "2015-10-03",
1191 | "2015-10-04",
1192 | "Element referenced by aria-describedby attribute",
1193 | "title attribute 6",
1194 | }),
1195 | new TestData("track", UIAControlType.Unknown,
1196 | additionalRequirement: ((elements, driver, ids) =>
1197 | {
1198 | var result = new TestCaseResultExt();
1199 | driver.ExecuteScript(Javascript.Track, timeout);
1200 |
1201 | if(!(bool)driver.ExecuteScript("return Modernizr.track && Modernizr.texttrackapi", timeout)) {
1202 | result.Result = ResultType.Half;
1203 | result.AddInfo("Element was not found to be supported by Modernizr");
1204 | }
1205 | return result;
1206 | }),
1207 | searchStrategy: (element => true)),
1208 | new TestData("video", UIAControlType.Group, null, keyboardElements: new List { "video1" },
1209 | additionalRequirement: ((elements, driver, ids) =>
1210 | {
1211 | var result = new TestCaseResultExt();
1212 | CheckChildNames(
1213 | new List {
1214 | "Play",
1215 | "Time elapsed",
1216 | "Seek",
1217 | "Time remaining",
1218 | "Zoom in",
1219 | "Show audio",
1220 | "Show captioning",
1221 | "Mute",
1222 | "Volume",
1223 | "Full screen" })(elements, driver, ids, result);
1224 | CheckVideoKeyboardInteractions(elements, driver, ids, result);
1225 | return result;
1226 | })),
1227 | new TestData("hidden-att", UIAControlType.Button, null,
1228 | additionalRequirement: ((elements, driver, ids) =>
1229 | {
1230 | var result = new TestCaseResultExt();
1231 |
1232 | var browserElement = EdgeA11yTools.FindBrowserDocument(0);
1233 |
1234 | if (elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) != 0)
1235 | {
1236 | result.Result = ResultType.Half;
1237 | result.AddInfo("Found " + elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) + " elements. Expected 0");
1238 | return result;
1239 | }
1240 |
1241 | //Make sure the text isn't showing up on the page
1242 | var five = (IUIAutomationElement5)
1243 | walker.GetFirstChildElement(
1244 | walker.GetFirstChildElement(elements[0]));//only have the pane element, take its grandchild
1245 |
1246 | List patternIds;
1247 | var names = five.GetPatterns(out patternIds);
1248 | var textPatternString = "TextPattern";
1249 | if (!names.Contains(textPatternString))
1250 | {
1251 | result.Result = ResultType.Half;
1252 | result.AddInfo("Pane did not support TextPattern, unable to search");
1253 | return result;
1254 | }
1255 |
1256 | var textPattern = (IUIAutomationTextPattern)five.GetCurrentPattern(
1257 | patternIds[names.IndexOf(textPatternString)]);
1258 |
1259 | var documentRange = textPattern.DocumentRange;
1260 |
1261 | var foundText = documentRange.GetText(1000);
1262 | if(foundText.Contains("HiDdEn TeXt"))
1263 | {
1264 | result.Result = ResultType.Half;
1265 | result.AddInfo("\nFound text that should have been hidden");
1266 | }
1267 |
1268 | //remove hidden attribute
1269 | driver.ExecuteScript(Javascript.RemoveHidden, timeout);
1270 |
1271 | //make sure the button show up now that their parents are not hidden
1272 | HashSet foundControlTypes;
1273 | elements = EdgeA11yTools.SearchChildren(browserElement, UIAControlType.Button, null, out foundControlTypes);
1274 | if (elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) != 1)
1275 | {
1276 | result.Result = ResultType.Half;
1277 | result.AddInfo("\nFound " + elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) + " elements. Expected 1");
1278 | }
1279 |
1280 | //remove aria-hidden attribute
1281 | driver.ExecuteScript(Javascript.RemoveAriaHidden, timeout);
1282 |
1283 | //both buttons should now be visible, since both aria-hidden and hidden attribute are missing
1284 | elements = EdgeA11yTools.SearchChildren(browserElement, UIAControlType.Button, null, out foundControlTypes);
1285 | if (elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) != 2)
1286 | {
1287 | result.Result = ResultType.Half;
1288 | result.AddInfo("\nFound " + elements.Count(e => GetControlTypeFromCode(e.CurrentControlType) != UIAControlType.Pane) + " elements. Expected 2");
1289 | }
1290 |
1291 | return result;
1292 | }),
1293 | searchStrategy: (element => true)),//take the pane
1294 | new TestData("required-att", UIAControlType.Edit,
1295 | additionalRequirement: (elements, driver, ids) =>
1296 | {
1297 | var result = new TestCaseResultExt();
1298 | driver.SendSpecialKeys("input1", WebDriverKey.Enter);
1299 | Thread.Sleep(TimeSpan.FromMilliseconds(500));
1300 | foreach(var element in elements){//there can only be one
1301 | if(element.CurrentControllerFor == null || element.CurrentControllerFor.Length == 0){
1302 | result.Result = ResultType.Half;
1303 | result.AddInfo("\nElement did not have controller for set");
1304 | }
1305 |
1306 | if(element.CurrentIsRequiredForForm != 1){
1307 | result.Result = ResultType.Half;
1308 | result.AddInfo("\nElement did not have IsRequiredForForm set to true");
1309 | }
1310 |
1311 | if(element.CurrentHelpText == null || element.CurrentHelpText.Length == 0){
1312 | result.Result = ResultType.Half;
1313 | result.AddInfo("\nElement did not have HelpText");
1314 | }
1315 | }
1316 |
1317 | return result;
1318 | }),
1319 | new TestData("placeholder-att", UIAControlType.Edit,
1320 | requiredNames:
1321 | new List {
1322 | "placeholder text 1",
1323 | "Label text 2:",
1324 | "Label text 3:",
1325 | "placeholder text 4",
1326 | "placeholder text 5",
1327 | "aria-placeholder text 6" },
1328 | requiredDescriptions:
1329 | new List {
1330 | "placeholder text 2",
1331 | "placeholder text 3",
1332 | "title text 4",
1333 | })
1334 | };
1335 | }
1336 |
1337 | ///
1338 | /// Convert an element into a pattern interface
1339 | ///
1340 | /// The pattern interface to implement
1341 | /// The friendly name of the pattern
1342 | /// The element to convert
1343 | /// The element as an implementation of the pattern
1344 | private static T GetPattern(string patternName, IUIAutomationElement element)
1345 | {
1346 | List patternNames;
1347 | List patternIds;
1348 |
1349 | patternNames = element.GetPatterns(out patternIds);
1350 | if (!patternNames.Contains(patternName))
1351 | {
1352 | return default(T);
1353 | }
1354 | else
1355 | {
1356 | T pattern = (T)((IUIAutomationElement5)element).GetCurrentPattern(patternIds[patternNames.IndexOf(patternName)]);
1357 | return pattern;
1358 | }
1359 | }
1360 |
1361 | ///
1362 | /// Helper method to tab until an element whose name contains the given string
1363 | /// reports that it has focus
1364 | ///
1365 | /// The parent of the target element
1366 | /// A string which the target element's name will contain
1367 | /// The id to send tabs to
1368 | /// The WebDriver
1369 | /// true if the element was found, false otherwise
1370 | private static bool TabToElementByName(IUIAutomationElement parent, string name, string tabId, DriverManager driver)
1371 | {
1372 | var tabs = 0;
1373 | var resets = 0;
1374 | var element = parent.GetAllDescendents(e => e.CurrentName.Contains(name)).First();
1375 | while (!(bool)element.GetCurrentPropertyValue(GetPropertyCode(UIAProperty.HasKeyboardFocus)))
1376 | {
1377 | driver.SendSpecialKeys(tabId, WebDriverKey.Tab);
1378 | if (++tabs > 20)
1379 | {
1380 | Javascript.ClearFocus(driver, 0);
1381 | tabs = 0;
1382 | resets++;
1383 | if (resets > 5)
1384 | {
1385 | return false;
1386 | }
1387 | }
1388 | }
1389 | return true;
1390 | }
1391 |
1392 | ///
1393 | /// Check basic keyboard interactions for the video control
1394 | ///
1395 | ///
1396 | ///
1397 | ///
1398 | /// An empty string if an element fails, otherwise an explanation
1399 | private static void CheckVideoKeyboardInteractions(List elements, DriverManager driver, List ids, TestCaseResultExt result)
1400 | {
1401 | string videoId = "video1";
1402 |
1403 | Func VideoPlaying = () => (bool)driver.ExecuteScript("return !document.getElementById('" + videoId + "').paused", 0);
1404 | Func