terminal/src/host/ft_uia/AccessibilityTests.cs
Michael Niksa 5d082ffe67
Helix Testing (#6992)
Use the Helix testing orchestration framework to run our Terminal LocalTests and Console Host UIA tests.

## References
#### Creates the following new issues:
- #7281 - re-enable local tests that were disabled to turn on Helix
- #7282 - re-enable UIA tests that were disabled to turn on Helix
- #7286 - investigate and implement appropriate compromise solution to how Skipped is handled by MUX Helix scripts

#### Consumes from:
- #7164 - The update to TAEF includes wttlog.dll. The WTT logs are what MUX's Helix scripts use to track the run state, convert to XUnit format, and notify both Helix and AzDO of what's going on.

#### Produces for:
- #671 - Making Terminal UIA tests is now possible
- #6963 - MUX's Helix scripts are already ready to capture PGO data on the Helix machines as certain tests run. Presuming we can author some reasonable scenarios, turning on the Helix environment gets us a good way toward automated PGO.

#### Related:
- #4490 - We lost the AzDO integration of our test data when I moved from the TAEF/VSTest adapter directly back to TE. Thanks to the WTTLog + Helix conversion scripts to XUnit + new upload phase, we have it back!

## PR Checklist
* [x] Closes #3838
* [x] I work here.
* [x] Literally adds tests.
* [ ] Should I update a testing doc in this repo?
* [x] Am core contributor. Hear me roar.
* [ ] Correct spell-checking the right way before merge.

## Detailed Description of the Pull Request / Additional comments
We have had two classes of tests that don't work in our usual build-machine testing environment:
1. Tests that require interactive UI automation or input injection (a.k.a. require a logged in user)
2. Tests that require the entire Windows Terminal to stand up (because our Xaml Islands dependency requires 1903 or later and the Windows Server instance for the build is based on 1809.)

The Helix testing environment solves both of these and is brought to us by our friends over in https://github.com/microsoft/microsoft-ui-xaml.

This PR takes a large portion of scripts and pipeline configuration steps from the Microsoft-UI-XAML repository and adjusts them for Terminal needs.
You can see the source of most of the files in either https://github.com/microsoft/microsoft-ui-xaml/tree/master/build/Helix or https://github.com/microsoft/microsoft-ui-xaml/tree/master/build/AzurePipelinesTemplates

Some of the modifications in the files include (but are not limited to) reasons like:
- Our test binaries are named differently than MUX's test binaries
- We don't need certain types of testing that MUX does.
- We use C++ and C# tests while MUX was using only C# tests (so the naming pattern and some of the parsing of those names is different e.g. :: separators in C++ and . separators in C#)
- Our pipeline phases work a bit differently than MUX and/or we need significantly fewer pieces to the testing matrix (like we don't test a wide variety of OS versions).

The build now runs in a few stages:
1. The usual build and run of unit tests/feature tests, packaging verification, and whatnot. This phase now also picks up and packs anything required for running tests in Helix into an artifact. (It also unifies the artifact name between the things Helix needs and the existing build outputs into the single `drop` artifact to make life a little easier.)
2. The Helix preparation build runs that picks up those artifacts, generates all the scripts required for Helix to understand the test modules/functions from our existing TAEF tests, packs it all up, and queues it on the Helix pool.
3. Helix generates a VM for our testing environment and runs all the TAEF tests that require it. The orchestrator at helix.dot.net watches over this and tracks the success/fail and progress of each module and function. The scripts from our MUX friends handle installing dependencies, making the system quiet for better reliability, detecting flaky tests and rerunning them, and coordinating all the log uploads (including for the subruns of tests that are re-run.)
4. A final build phase is run to look through the results with the Helix API and clean up the marking of tests that are flaky, link all the screenshots and console output logs into the AzDO tests panel, and other such niceities.

We are set to run Helix tests on the Feature test policy of only x64 for now. 

Additionally, because the set up of the Helix VMs takes so long, we are *NOT* running these in PR trigger right now as I believe we all very much value our 15ish minute PR turnaround (and the VM takes another 15 minutes to just get going for whatever reason.) For now, they will only run as a rolling build on master after PRs are merged. We should still know when there's an issue within about an hour of something merging and multiple PRs merging fast will be done on the rolling build as a batch run (not one per).

In addition to setting up the entire Helix testing pipeline for the tests that require it, I've preserved our classic way of running unit and feature tests (that don't require an elaborate environment) directly on the build machines. But with one bonus feature... They now use some of the scripts from MUX to transform their log data and report it to AzDO so it shows up beautifully in the build report. (We used to have this before I removed the MStest/VStest wrapper for performance reasons, but now we can have reporting AND performance!) See https://dev.azure.com/ms/terminal/_build/results?buildId=101654&view=ms.vss-test-web.build-test-results-tab for an example. 

I explored running all of the tests on Helix but.... the Helix setup time is long and the resources are more expensive. I felt it was better to preserve the "quick signal" by continuing to run these directly on the build machine (and skipping the more expensive/slow Helix setup if they fail.) It also works well with the split between PR builds not running Helix and the rolling build running Helix. PR builds will get a good chunk of tests for a quick turn around and the rolling build will finish the more thorough job a bit more slowly.

## Validation Steps Performed
- [x] Ran the updated pipelines with Pull Request configuration ensuring that Helix tests don't run in the usual CI
- [x] Ran with simulation of the rolling build to ensure that the tests now running in Helix will pass. All failures marked for follow on in reference issues.
2020-08-18 18:23:24 +00:00

641 lines
30 KiB
C#

//----------------------------------------------------------------------------------------------------------------------
// <copyright file="AccessibilityTests.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// </copyright>
// <summary>UI Automation tests for the certain key presses.</summary>
//----------------------------------------------------------------------------------------------------------------------
using System;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Automation;
using System.Windows.Automation.Text;
using WEX.TestExecution;
using WEX.TestExecution.Markup;
using WEX.Logging.Interop;
using Conhost.UIA.Tests.Common;
using Conhost.UIA.Tests.Common.NativeMethods;
using Conhost.UIA.Tests.Elements;
using OpenQA.Selenium;
using System.Drawing;
using System.Runtime.InteropServices;
namespace Conhost.UIA.Tests
{
class InvalidElementException : Exception
{
}
[TestClass]
class AccessibilityTests
{
public TestContext TestContext { get; set; }
private AutomationElement GetWindowUiaElement(CmdApp app)
{
IntPtr handle = app.GetWindowHandle();
AutomationElement windowUiaElement = AutomationElement.FromHandle(handle);
return windowUiaElement;
}
private AutomationElement GetTextAreaUiaElement(CmdApp app)
{
AutomationElement windowUiaElement = GetWindowUiaElement(app);
AutomationElementCollection descendants = windowUiaElement.FindAll(TreeScope.Descendants, Condition.TrueCondition);
for (int i = 0; i < descendants.Count; ++i)
{
AutomationElement poss = descendants[i];
if (poss.Current.AutomationId.Equals("Text Area"))
{
return poss;
}
}
throw new InvalidElementException();
}
private int _GetTotalRows(CmdApp app)
{
WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX screenBufferInfo = app.GetScreenBufferInfo();
return screenBufferInfo.dwSize.Y;
}
private void _ClearScreenBuffer(CmdApp app)
{
IntPtr outHandle = app.GetStdOutHandle();
WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX screenInfo = new WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX();
screenInfo.cbSize = (uint)Marshal.SizeOf(screenInfo);
WinCon.GetConsoleScreenBufferInfoEx(outHandle, ref screenInfo);
int charCount = screenInfo.dwSize.X * screenInfo.dwSize.Y;
string writeString = new string(' ', charCount);
WinCon.COORD coord = new WinCon.COORD();
coord.X = 0;
coord.Y = 0;
UInt32 charsWritten = 0;
WinCon.WriteConsoleOutputCharacter(outHandle,
writeString,
(uint)charCount,
coord,
ref charsWritten);
Verify.AreEqual((UInt32)charCount, charsWritten);
}
private void _WriteCharTestText(CmdApp app)
{
IntPtr outHandle = app.GetStdOutHandle();
WinCon.COORD coord = new WinCon.COORD();
coord.X = 0;
coord.Y = 0;
string row1 = "1234567890";
string row2 = " abcdefghijk";
UInt32 charsWritten = 0;
WinCon.WriteConsoleOutputCharacter(outHandle,
row1,
(uint)row1.Length,
coord,
ref charsWritten);
coord.Y = 1;
WinCon.WriteConsoleOutputCharacter(outHandle,
row2,
(uint)row2.Length,
coord,
ref charsWritten);
}
private void _FillOutputBufferWithData(CmdApp app)
{
for (int i = 0; i < _GetTotalRows(app) * 2 / 3; ++i)
{
// each echo command uses up 3 lines in the buffer:
// 1. output text
// 2. newline
// 3. new prompt line
app.UIRoot.SendKeys("echo ");
app.UIRoot.SendKeys(i.ToString());
app.UIRoot.SendKeys(Keys.Enter);
}
}
[TestMethod]
public void CanAccessAccessibilityTree()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
AutomationElement windowUiaElement = GetWindowUiaElement(app);
Verify.IsTrue(windowUiaElement.Current.AutomationId.Equals("Console Window"));
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanAccessTextAreaUiaElement()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
Verify.IsTrue(textAreaUiaElement != null);
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanGetDocumentRangeText()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
// get the text from uia api
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange documentRange = textPattern.DocumentRange;
string allText = documentRange.GetText(-1);
// get text from console api
IntPtr hConsole = app.GetStdOutHandle();
using (ViewportArea area = new ViewportArea(app))
{
WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX screenInfo = app.GetScreenBufferInfo();
Rectangle rect = new Rectangle(0, 0, screenInfo.dwSize.X, screenInfo.dwSize.Y);
IEnumerable<string> viewportText = area.GetLinesInRectangle(hConsole, rect);
// the uia api does not return spaces beyond the last
// non -whitespace character so we need to trim those from
// the viewportText. The uia api also inserts \r\n to indicate
// a new linen so we need to add those back in after trimming.
string consoleText = "";
for (int i = 0; i < viewportText.Count(); ++i)
{
consoleText += viewportText.ElementAt(i).Trim() + "\r\n";
}
consoleText = consoleText.Trim();
allText = allText.Trim();
// compare
Verify.IsTrue(consoleText.Equals(allText));
}
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanGetTextAtCharacterLevel()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
const int noMaxLength = -1;
const string row1 = "1234567890";
const string row2 = " abcdefghijk";
_ClearScreenBuffer(app);
_WriteCharTestText(app);
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] ranges = textPattern.GetVisibleRanges();
TextPatternRange range = ranges[0].Clone();
// should be able to get each char in row1
range.ExpandToEnclosingUnit(TextUnit.Character);
foreach (char ch in row1)
{
string text = range.GetText(noMaxLength);
Verify.AreEqual(ch.ToString(), text);
range.Move(TextUnit.Character, 1);
}
// should be able to get each char in row2, including starting spaces
range = ranges[1].Clone();
range.ExpandToEnclosingUnit(TextUnit.Character);
foreach (char ch in row2)
{
string text = range.GetText(noMaxLength);
Verify.AreEqual(ch.ToString(), text);
range.Move(TextUnit.Character, 1);
}
// taking half of each row should return correct text with
// spaces if they appear before the last non-whitespace char
range = ranges[0].Clone();
range.MoveEndpointByUnit(TextPatternRangeEndpoint.Start, TextUnit.Character, 8);
range.MoveEndpointByUnit(TextPatternRangeEndpoint.End, TextUnit.Character, 8);
Verify.AreEqual("90\r\n abcde", range.GetText(noMaxLength));
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanGetVisibleRange()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
// get the ranges from uia api
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] ranges = textPattern.GetVisibleRanges();
// get the ranges from the console api
WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX screenInfo = app.GetScreenBufferInfo();
int viewportHeight = screenInfo.srWindow.Bottom - screenInfo.srWindow.Top + 1;
// we should have one range per line in the viewport
Verify.AreEqual(ranges.GetLength(0), viewportHeight);
// each line should have the same text
ViewportArea viewport = new ViewportArea(app);
IntPtr hConsole = app.GetStdOutHandle();
for (int i = 0; i < viewportHeight; ++i)
{
Rectangle rect = new Rectangle(0, i, screenInfo.dwSize.X, 1);
IEnumerable<string> text = viewport.GetLinesInRectangle(hConsole, rect);
Verify.AreEqual(text.ElementAt(0).Trim(), ranges[i].GetText(-1).Trim());
}
}
}
/*
// TODO this is commented out because it will fail. It fails because the c# api of RangeFromPoint
// throws an exception when passed a point that is outside the bounds of the window, which is
// allowed in the c++ version and exactly what we want to test. Another way to test this case needs
// to be found that doesn't go through the c# api.
[TestMethod]
public void CanGetRangeFromPoint()
{
using (RegistryHelper reg = new RegistryHelper())
{
reg.BackupRegistry();
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
// RangeFromPoint returns the range closest to the point provided
// so we have three cases along the y dimension:
// - point above the console window
// - point in the console window
// - point below the console window
// get the window dimensions and pick the point locations
IntPtr handle = app.GetWindowHandle();
User32.RECT windowRect;
User32.GetWindowRect(handle, out windowRect);
List<System.Windows.Point> points = new List<System.Windows.Point>();
int middleOfWindow = (windowRect.bottom + windowRect.top) / 2;
const int windowEdgeOffset = 10;
// x doesn't matter until we support more ranges than lines
points.Add(new System.Windows.Point(windowRect.left, windowRect.top - windowEdgeOffset));
points.Add(new System.Windows.Point(windowRect.left, middleOfWindow));
points.Add(new System.Windows.Point(windowRect.left, windowRect.bottom + windowEdgeOffset));
// get the ranges from uia api
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
List<TextPatternRange> textPatternRanges = new List<TextPatternRange>();
foreach (System.Windows.Point p in points)
{
textPatternRanges.Add(textPattern.RangeFromPoint(p));
}
// ranges should be in correct order (starting at top and
// going down screen)
Rect lastBoundingRect = textPatternRanges.ElementAt(0).GetBoundingRectangles()[0];
foreach (TextPatternRange range in textPatternRanges)
{
Rect[] boundingRects = range.GetBoundingRectangles();
// since the ranges returned by RangeFromPoint are supposed to be degenerate,
// there should be only one bounding rect per TextPatternRange
Verify.AreEqual(boundingRects.GetLength(0), 1);
Verify.IsTrue(boundingRects[0].Top >= lastBoundingRect.Top);
lastBoundingRect = boundingRects[0];
}
}
}
}
*/
[TestMethod]
public void CanCloneTextRangeProvider()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange textPatternRange = textPattern.DocumentRange;
// clone it
TextPatternRange copyRange = textPatternRange.Clone();
Verify.IsTrue(copyRange.Compare(textPatternRange));
// change the copy and make sure the compare fails
copyRange.MoveEndpointByRange(TextPatternRangeEndpoint.End, copyRange, TextPatternRangeEndpoint.Start);
Verify.IsFalse(copyRange.Compare(textPatternRange));
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanCompareTextRangeProviderEndpoints()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange textPatternRange = textPattern.DocumentRange;
// comparing an endpoint to itself should be the same
Verify.AreEqual(0, textPatternRange.CompareEndpoints(TextPatternRangeEndpoint.Start,
textPatternRange,
TextPatternRangeEndpoint.Start));
// comparing an earlier endpoint to a later one should be negative
Verify.IsGreaterThan(0, textPatternRange.CompareEndpoints(TextPatternRangeEndpoint.Start,
textPatternRange,
TextPatternRangeEndpoint.End));
// comparing a later endpoint to an earlier one should be positive
Verify.IsLessThan(0, textPatternRange.CompareEndpoints(TextPatternRangeEndpoint.End,
textPatternRange,
TextPatternRangeEndpoint.Start));
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanExpandToEnclosingUnitTextRangeProvider()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
var sbiex = app.GetScreenBufferInfo();
sbiex.dwSize.Y = (short)(2 * sbiex.srWindow.Height);
app.SetScreenBufferInfo(sbiex);
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] visibleRanges = textPattern.GetVisibleRanges();
TextPatternRange testRange = visibleRanges.First().Clone();
// change testRange to a degenerate range and then expand to a line
testRange.MoveEndpointByRange(TextPatternRangeEndpoint.End, testRange, TextPatternRangeEndpoint.Start);
Verify.AreEqual(0, testRange.CompareEndpoints(TextPatternRangeEndpoint.Start,
testRange,
TextPatternRangeEndpoint.End));
testRange.ExpandToEnclosingUnit(TextUnit.Line);
Verify.IsTrue(testRange.Compare(visibleRanges[0]));
// expand to document size
testRange.ExpandToEnclosingUnit(TextUnit.Document);
Verify.IsTrue(testRange.Compare(textPattern.DocumentRange));
// shrink back to a line
testRange.ExpandToEnclosingUnit(TextUnit.Line);
Verify.IsTrue(testRange.Compare(visibleRanges[0]));
// make the text buffer start to cycle its buffer
_FillOutputBufferWithData(app);
// expand to document range again
testRange.ExpandToEnclosingUnit(TextUnit.Document);
Verify.IsTrue(testRange.Compare(textPattern.DocumentRange));
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanMoveRange()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
var sbiex = app.GetScreenBufferInfo();
sbiex.dwSize.Y = (short)(2 * sbiex.srWindow.Height);
app.SetScreenBufferInfo(sbiex);
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] visibleRanges = textPattern.GetVisibleRanges();
TextPatternRange testRange = visibleRanges.First().Clone();
// assumes range is at the top of the screen buffer
Action<TextPatternRange> testMovement = delegate (TextPatternRange range)
{
// the range is at the top of the screen
// buffer, we shouldn't be able to move up.
int moveAmount = range.Move(TextUnit.Line, -1);
Verify.AreEqual(0, moveAmount);
// move to the bottom of the screen
// - 1 because we're already on the 0th row
int rowsToMove = _GetTotalRows(app) - 1;
moveAmount = range.Move(TextUnit.Line, rowsToMove);
Verify.AreEqual(rowsToMove, moveAmount);
// try to move one more row down, we should not be able to
moveAmount = range.Move(TextUnit.Line, 1);
Verify.AreEqual(0, moveAmount);
// move the range up to the top again, one row at a time,
// making sure that we have only one line being encompassed
// by the range. We check this by counting the number of
// bounding rectangles that represent the range.
for (int i = 0; i < rowsToMove; ++i)
{
moveAmount = range.Move(TextUnit.Line, -1);
// we need to scroll into view or getting the boundary
// rectangles might return 0
Verify.AreEqual(-1, moveAmount);
range.ScrollIntoView(true);
Rect[] boundingRects = range.GetBoundingRectangles();
Verify.AreEqual(1, boundingRects.GetLength(0));
}
// and back down to the bottom, one row at a time
for (int i = 0; i < rowsToMove; ++i)
{
moveAmount = range.Move(TextUnit.Line, 1);
// we need to scroll into view or getting the boundary
// rectangles might return 0
Verify.AreEqual(1, moveAmount);
range.ScrollIntoView(true);
Rect[] boundingRects = range.GetBoundingRectangles();
Verify.AreEqual(1, boundingRects.GetLength(0));
}
};
testMovement(testRange);
// test again with unaligned text buffer and screen buffer
_FillOutputBufferWithData(app);
Globals.WaitForTimeout();
visibleRanges = textPattern.GetVisibleRanges();
testRange = visibleRanges.First().Clone();
// move range back to the top
while (true)
{
int moveCount = testRange.Move(TextUnit.Line, -1);
if (moveCount == 0)
{
break;
}
}
testMovement(testRange);
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanMoveEndpointByUnitNearTopBoundary()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
var sbiex = app.GetScreenBufferInfo();
sbiex.dwSize.Y = (short)(2 * sbiex.srWindow.Height);
app.SetScreenBufferInfo(sbiex);
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] visibleRanges = textPattern.GetVisibleRanges();
TextPatternRange testRange = visibleRanges.First().Clone();
// assumes that range is a line range at the top of the screen buffer
Action<TextPatternRange> testTopBoundary = delegate (TextPatternRange range)
{
// the first visible range is at the top of the screen
// buffer, we shouldn't be able to move the starting endpoint up
int moveAmount = range.MoveEndpointByUnit(TextPatternRangeEndpoint.Start, TextUnit.Line, -1);
Verify.AreEqual(0, moveAmount);
// we should be able to move the ending endpoint back, creating a degenerate range
moveAmount = range.MoveEndpointByUnit(TextPatternRangeEndpoint.End, TextUnit.Line, -1);
Verify.AreEqual(-1, moveAmount);
// the range should now be degenerate and the ending
// endpoint should not be able to be moved back again
string rangeText = range.GetText(-1);
Verify.AreEqual("", rangeText);
moveAmount = range.MoveEndpointByUnit(TextPatternRangeEndpoint.End, TextUnit.Line, -1);
Verify.AreEqual(-1, moveAmount);
};
testTopBoundary(testRange);
// we want to test that the boundaries are still observed
// when the screen buffer index and text buffer index don't align.
// write a bunch of text to the screen to fill up the text
// buffer and make it start to reuse its buffer
_FillOutputBufferWithData(app);
Globals.WaitForTimeout();
// move all the way to the bottom
visibleRanges = textPattern.GetVisibleRanges();
testRange = visibleRanges.Last().Clone();
while (true)
{
int moved = testRange.Move(TextUnit.Line, 1);
if (moved == 0)
{
break;
}
}
// we're at the bottom of the screen buffer, so move back to the top
// so we can test
int rowsToMove = -1 * (_GetTotalRows(app) - 1);
int moveCount = testRange.Move(TextUnit.Line, rowsToMove);
Verify.AreEqual(rowsToMove, moveCount);
testRange.ScrollIntoView(true);
testTopBoundary(testRange);
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanMoveEndpointByUnitNearBottomBoundary()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
var sbiex = app.GetScreenBufferInfo();
sbiex.dwSize.Y = (short)(2 * sbiex.srWindow.Height);
app.SetScreenBufferInfo(sbiex);
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] visibleRanges = textPattern.GetVisibleRanges();
TextPatternRange testRange = visibleRanges.First().Clone();
// assumes that range is a line range at the bottom of the screen buffer
Action<TextPatternRange> testBottomBoundary = delegate (TextPatternRange range)
{
// the range is at the bottom of the screen buffer, we
// shouldn't be able to move the endpoint endpoint down
int moveAmount = range.MoveEndpointByUnit(TextPatternRangeEndpoint.End, TextUnit.Line, 1);
Verify.AreEqual(0, moveAmount);
// we shouldn't be able to move the starting endpoint down either
moveAmount = range.MoveEndpointByUnit(TextPatternRangeEndpoint.Start, TextUnit.Line, 1);
Verify.AreEqual(0, moveAmount);
};
// move the range to the bottom of the screen
int rowsToMove = _GetTotalRows(app) - 1;
int moveCount = testRange.Move(TextUnit.Line, rowsToMove);
Verify.AreEqual(rowsToMove, moveCount);
testBottomBoundary(testRange);
// we want to test that the boundaries are still observed
// when the screen buffer index and text buffer index don't align.
// write a bunch of text to the screen to fill up the text
// buffer and make it start to reuse its buffer
_FillOutputBufferWithData(app);
Globals.WaitForTimeout();
// move all the way to the top
visibleRanges = textPattern.GetVisibleRanges();
testRange = visibleRanges.First().Clone();
while (true)
{
int moved = testRange.Move(TextUnit.Line, -1);
if (moved == 0)
{
break;
}
}
// we're at the top of the screen buffer, so move back to the bottom
// so we can test
rowsToMove = _GetTotalRows(app) - 1;
moveCount = testRange.Move(TextUnit.Line, rowsToMove);
Verify.AreEqual(rowsToMove, moveCount);
testRange.ScrollIntoView(true);
testBottomBoundary(testRange);
}
}
[TestMethod]
[TestProperty("Ignore", "True")] // GH#7282 - investigate and reenable
public void CanGetBoundingRectangles()
{
using (CmdApp app = new CmdApp(CreateType.ProcessOnly, TestContext))
{
AutomationElement textAreaUiaElement = GetTextAreaUiaElement(app);
TextPattern textPattern = textAreaUiaElement.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
TextPatternRange[] visibleRanges = textPattern.GetVisibleRanges();
// copy the first range
TextPatternRange firstRange = visibleRanges[0].Clone();
// only one bounding rect should be returned for the one line
Rect[] boundingRects = firstRange.GetBoundingRectangles();
Verify.AreEqual(1, boundingRects.GetLength(0));
// expand to two lines, verify we get a bounding rect per line
firstRange.MoveEndpointByRange(TextPatternRangeEndpoint.End, visibleRanges[1], TextPatternRangeEndpoint.End);
boundingRects = firstRange.GetBoundingRectangles();
Verify.AreEqual(2, boundingRects.GetLength(0));
}
}
}
}