Enhance E2E Reporting + CI Reliability (#325)

* Yarn

* Enhance E2E Reporting + CI Reliability

* Nit - Formatting

* Removing sleep call - originated from RNW. Will observe how this affects the pipeline
This commit is contained in:
Samuel Freiberg 2020-07-10 11:51:09 -07:00 коммит произвёл GitHub
Родитель c5b508f7fa
Коммит 00aa319f77
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
37 изменённых файлов: 541 добавлений и 166 удалений

Просмотреть файл

@ -0,0 +1,46 @@
parameters:
platform:
applicationType:
buildArtifacts:
steps:
# Generate reports. This task doesn't end until a ctrl+c is pressed. I'm not sure how to end it in this case, so
# I added a 1 minute timer. It should always generate the report in this time, then the task timeouts and continues on the pipeline.
# continueOnError is true because the pipeline should continue even on failure
- script: |
yarn report
workingDirectory: apps\${{parameters.platform}}
continueOnError: true
timeoutInMinutes: 1
displayName: "generate ${{parameters.platform}} report"
condition: and (succeededOrFailed(), eq(${{parameters.buildArtifacts}}, 'success'))
# Copy errorShots to FluentTesterDump
- task: CopyFiles@2
inputs:
sourceFolder: $(Build.SourcesDirectory)\apps\${{parameters.platform}}\errorShots
targetFolder: $(Build.StagingDirectory)/E2E_${{parameters.applicationType}}_Dump
displayName: "Copy tree dump screenshots"
condition: and (succeededOrFailed(), eq(${{parameters.buildArtifacts}}, 'success'))
# Copy appium log to FluentTesterDump
- task: CopyFiles@2
inputs:
sourceFolder: $(Build.SourcesDirectory)\apps\${{parameters.platform}}\reports\
contents: "*"
targetFolder: $(Build.StagingDirectory)/E2E_${{parameters.applicationType}}_Dump
displayName: "Copy tree dump report logs"
condition: and (succeededOrFailed(), eq(${{parameters.buildArtifacts}}, 'success'))
- powershell: |
Move-Item -Path $(Build.SourcesDirectory)\apps\${{parameters.platform}}\allure-report -Destination $(Build.StagingDirectory)/E2E_${{parameters.applicationType}}_Dump
displayName: "Copy allure report"
condition: and (succeededOrFailed(), eq(${{parameters.buildArtifacts}}, 'success'))
# Publish FluentTesterDump
- task: PublishBuildArtifacts@1
inputs:
artifactName: E2E_${{parameters.applicationType}}_Dump
pathtoPublish: $(Build.StagingDirectory)/E2E_${{parameters.applicationType}}_Dump
displayName: "Publish Artifact:E2E_${{parameters.applicationType}}_Dump"
condition: and (succeededOrFailed(), eq(${{parameters.buildArtifacts}}, 'success'))

Просмотреть файл

@ -38,18 +38,34 @@ steps:
workingDirectory: apps\windows
displayName: "run UWP app"
# Wait for app to launch. A workaround to avoid WinAppDriver error: Failed to locate opened application window with appId
- powershell: |
Start-Sleep -Seconds 60
displayName: "Wait for app to launch"
# Kill FluentTester, leave server up and running. This was the only way I could get the server continuously running
# as a separate process.
- powershell: |
Stop-Process -Name FluentTester
displayName: "Kill FluentTester Process"
# Creates a variable that determines whether the previous build tasks succeeded.
# Usage: We want the tasks that generate reports to run for both passing/failing E2E testing tasks. In order to do so, we need to make
# those reporting tasks run even on when certain previous tasks fail. This variable allows us to differentiate build failures from
# E2E testing failures. Thus, if this variable != "Success", we know the build failed, and to not run the reporting tasks.
- task: PowerShell@2
inputs:
targetType: "inline"
script: |
Write-Host "##vso[task.setvariable variable=task.Build.status]Success"
condition: succeeded()
displayName: "Create success build variable"
- script: |
yarn e2etest
workingDirectory: apps\windows
displayName: "run E2E UWP tests"
condition: succeeded()
# The following condition (using task.Build.status variable) make it so the reports generate even if the E2E tasks fails,
# but not if the initial repo build steps fail.
- template: e2e-publish-artifacts.yml
parameters:
applicationType: UWP
platform: windows
buildArtifacts: variables['task.Build.status']

Просмотреть файл

@ -4,7 +4,26 @@ steps:
workingDirectory: apps/win32
displayName: "yarn bundle"
# Creates a variable that determines whether the previous build tasks succeeded.
# Usage: We want the tasks that generate reports to run for both passing/failing E2E testing tasks. In order to do so, we need to make
# those reporting tasks run even on when certain previous tasks fail. This variable allows us to differentiate build failures from
# E2E testing failures. Thus, if this variable != "Success", we know the build failed, and to not run the reporting tasks.
- task: PowerShell@2
inputs:
targetType: "inline"
script: |
Write-Host "##vso[task.setvariable variable=task.Build.status]Success"
condition: succeeded()
displayName: "Create success build variable"
- script: |
yarn e2etest
workingDirectory: apps/win32
displayName: "run E2E Win32 tests"
condition: succeeded()
- template: e2e-publish-artifacts.yml
parameters:
applicationType: win32
platform: win32
buildArtifacts: variables['task.Build.status']

2
.gitignore поставляемый
Просмотреть файл

@ -6,6 +6,8 @@ yarn-debug.log*
yarn-error.log*
**/reports/
**/errorShots/
**/allure-results/
**/allure-report/
# Runtime data
pids

Просмотреть файл

@ -12,64 +12,87 @@ import SeparatorTestPage from '../../Separator/pages/SeparatorTestPage.win';
import TextTestPage from '../../Text/pages/TextTestPage.win';
import ThemeTestPage from '../../Theme/pages/ThemeTestPage.win';
describe('Click on each test page and check if it renders', function() {
const BOOT_APP_TIMEOUT = 60000;
const PAGE_TIMEOUT = 3000;
// Before testing begins, allow up to 60 seconds for app to open
describe('Open the app', function () {
it('Boot app', () => {
BootTestPage.waitForPageDisplayed(BOOT_APP_TIMEOUT);
expect(BootTestPage.isPageLoaded()).toBeTruthy();
});
});
describe('Click on each test page and check if it renders', function () {
it('Button Test Page', () => {
BootTestPage.clickAndGoToButtonPage();
ButtonTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(ButtonTestPage.isPageLoaded()).toBeTruthy();
});
it('Callout Test Page', () => {
BootTestPage.clickAndGoToCalloutPage();
CalloutTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(CalloutTestPage.isPageLoaded()).toBeTruthy();
});
it('Checkbox Test Page', () => {
BootTestPage.clickAndGoToCheckboxPage();
CheckboxTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(CheckboxTestPage.isPageLoaded()).toBeTruthy();
});
it('FocusTrapZone Test Page', () => {
BootTestPage.clickAndGoToFocusTrapZonePage();
FocusTrapZoneTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(FocusTrapZoneTestPage.isPageLoaded()).toBeTruthy();
});
it('Link Test Page', () => {
BootTestPage.clickAndGoToLinkPage();
LinkTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(LinkTestPage.isPageLoaded()).toBeTruthy();
});
it('Persona Test Page', () => {
BootTestPage.clickAndGoToPersonaPage();
PersonaTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(PersonaTestPage.isPageLoaded()).toBeTruthy();
});
it('PersonaCoin Test Page', () => {
BootTestPage.clickAndGoToPersonaCoinPage();
PersonaCoinTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(PersonaCoinTestPage.isPageLoaded()).toBeTruthy();
});
it('Pressable Test Page', () => {
BootTestPage.clickAndGoToPressablePage();
PressableTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(PressableTestPage.isPageLoaded()).toBeTruthy();
});
it('RadioGroup Test Page', () => {
BootTestPage.clickAndGoToRadioGroupPage();
RadioGroupTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(RadioGroupTestPage.isPageLoaded()).toBeTruthy();
});
it('Separator Test Page', () => {
BootTestPage.clickAndGoToSeparatorPage();
SeparatorTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(SeparatorTestPage.isPageLoaded()).toBeTruthy();
});
it('Text Test Page', () => {
BootTestPage.clickAndGoToTextPage();
TextTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(TextTestPage.isPageLoaded()).toBeTruthy();
});
it('Theme Test Page', () => {
BootTestPage.clickAndGoToThemePage();
ThemeTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(ThemeTestPage.isPageLoaded()).toBeTruthy();
});
});
});

Просмотреть файл

@ -10,61 +10,75 @@ import SeparatorTestPage from '../../Separator/pages/SeparatorTestPage.win';
import TextTestPage from '../../Text/pages/TextTestPage.win';
import ThemeTestPage from '../../Theme/pages/ThemeTestPage.win';
const PAGE_TIMEOUT = 45000;
const BOOT_APP_TIMEOUT = 60000;
const PAGE_TIMEOUT = 3000;
// Before testing begins, allow 45 seconds for bundle to load (WebDriverIO)
beforeAll(() => {
BootTestPage.waitForPageLoaded(PAGE_TIMEOUT);
// Before testing begins, allow up to 60 seconds for bundle to load (WebDriverIO)
describe('Open the app', function() {
it('Boot app', () => {
BootTestPage.waitForPageDisplayed(BOOT_APP_TIMEOUT);
expect(BootTestPage.isPageLoaded()).toBeTruthy();
});
});
describe('Click on each test page and check if it renders', function() {
describe('Click on each test page and check if it renders', function () {
it('Button Test Page', () => {
BootTestPage.clickAndGoToButtonPage();
ButtonTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(ButtonTestPage.isPageLoaded()).toBeTruthy();
});
it('Callout Test Page', () => {
BootTestPage.clickAndGoToCalloutPage();
CalloutTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(CalloutTestPage.isPageLoaded()).toBeTruthy();
});
it('Checkbox Test Page', () => {
BootTestPage.clickAndGoToCheckboxPage();
CheckboxTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(CheckboxTestPage.isPageLoaded()).toBeTruthy();
});
it('Link Test Page', () => {
BootTestPage.clickAndGoToLinkPage();
LinkTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(LinkTestPage.isPageLoaded()).toBeTruthy();
});
it('PersonaCoin Test Page', () => {
BootTestPage.clickAndGoToPersonaCoinPage();
PersonaCoinTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(PersonaCoinTestPage.isPageLoaded()).toBeTruthy();
});
it('Pressable Test Page', () => {
BootTestPage.clickAndGoToPressablePage();
PressableTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(PressableTestPage.isPageLoaded()).toBeTruthy();
});
it('RadioGroup Test Page', () => {
BootTestPage.clickAndGoToRadioGroupPage();
RadioGroupTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(RadioGroupTestPage.isPageLoaded()).toBeTruthy();
});
it('Separator Test Page', () => {
BootTestPage.clickAndGoToSeparatorPage();
SeparatorTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(SeparatorTestPage.isPageLoaded()).toBeTruthy();
});
it('Text Test Page', () => {
BootTestPage.clickAndGoToTextPage();
TextTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(TextTestPage.isPageLoaded()).toBeTruthy();
});
it('Theme Test Page', () => {
BootTestPage.clickAndGoToThemePage();
ThemeTestPage.waitForPageDisplayed(PAGE_TIMEOUT);
expect(ThemeTestPage.isPageLoaded()).toBeTruthy();
});
});
});

Просмотреть файл

@ -5,6 +5,10 @@ class ButtonTestPage extends BasePage {
get _testPage() {
return By(BUTTON_TESTPAGE);
}
get _pageName() {
return BUTTON_TESTPAGE;
}
}
export default new ButtonTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class CalloutTestPage extends BasePage {
get _testPage() {
return By(CALLOUT_TESTPAGE);
}
get _pageName() {
return CALLOUT_TESTPAGE;
}
}
export default new CalloutTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class CheckboxTestPage extends BasePage {
get _testPage() {
return By(CHECKBOX_TESTPAGE);
}
get _pageName() {
return CHECKBOX_TESTPAGE;
}
}
export default new CheckboxTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class FocusTrapZonePage extends BasePage {
get _testPage() {
return By(FOCUSTRAPZONE_TESTPAGE);
}
get _pageName() {
return FOCUSTRAPZONE_TESTPAGE;
}
}
export default new FocusTrapZonePage();

Просмотреть файл

@ -5,6 +5,10 @@ class LinkTestPage extends BasePage {
get _testPage() {
return By(LINK_TESTPAGE);
}
get _pageName() {
return LINK_TESTPAGE;
}
}
export default new LinkTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class PersonaTestPage extends BasePage {
get _testPage() {
return By(PERSONA_TESTPAGE);
}
get _pageName() {
return PERSONA_TESTPAGE;
}
}
export default new PersonaTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class PersonaCoinTestPage extends BasePage {
get _testPage() {
return By(PERSONACOIN_TESTPAGE);
}
get _pageName() {
return PERSONACOIN_TESTPAGE;
}
}
export default new PersonaCoinTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class PressableTestPage extends BasePage {
get _testPage() {
return By(PRESSABLE_TESTPAGE);
}
get _pageName() {
return PRESSABLE_TESTPAGE;
}
}
export default new PressableTestPage();

Просмотреть файл

@ -7,6 +7,9 @@
- **NOTE:** Please make sure you grab all of the items listed there and the appropriate versions.
- [WinAppDriver](https://github.com/microsoft/WinAppDriver) - Version 1.1
- Enable [_Developer Mode_](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) in Windows settings
- [Java 1.8](https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html) (Optional) - Used for generating in-depth after-action reports. More information in "Debugging E2E Failures" section below.
- [Allure Command-Line](https://www.npmjs.com/package/allure-commandline) (Optional) - Used for creating in-depth reporting.
- `npm install -g allure-commandline`
### UWP Additional Prerequisites
@ -92,3 +95,57 @@ describe('Click on each test page and check if it renders', function() {
});
});
```
# Debugging E2E Failures (Locally)
If one tests fails, it will cause every subsequent test to fail as well. Due to this structure, if you get a failing E2E run, you should find the **first** failing test and focus on fixing that one.
Having a clear and concise report on testing failures is key in efficient debugging. We're utilizing two report generators:
1. **Spec Reporter** - Low overhead, easy to read, automatically runs with E2E testing. However, less information and less reliable.
2. **Allure Reporter** - Creates in-depth reports with key information about each test. Must have Java 1.8 downloaded to create reports and requires running additional script after E2E testing is complete.
## Using Spec Reporter
You can view the spec report right as E2E testing is finished. It shows the failing tests and a brief explanation of what went wrong. However, in some cases when a redbox error occurs (mostly in Win32), these messages and report will not exist. This is because the FluentTester app becomes a non-responsive window, and WebDriverIO cannot close the window, which leaves the spec reporter in a bad state. In this case, I would recommend using the Allure Reporter.
In the example below, the SVG test is the one failing the run, and at the bottom, you can see an error message.
![E2E Error Debugging](../../../../assets/E2E/E2E_spec_reporter.png)
When running E2E locally, after failing an E2E run, you will get a screenshot of the error in /errorShots/ of the platform you tested.
## Using Allure Reporter
Allure Framework is a flexible, lightweight multi-language test report tool that not only shows a very concise representation of what have been tested in a neat web report form, but allows everyone participating in the development process to extract maximum of useful information from everyday execution of tests.
After E2E testing runs, allure creates a folder of XML files with all relevant information from the tests. In order to generate the report, you need to run the following command:
- `yarn generate-report`
This will bundle all the generated information and create a report for you to read.
# Debugging E2E Failures (CI Pipeline)
When an E2E test run fails within our CI, crucial information is output to Azure-Pipelines to help you debug the failure. Follow these steps:
1. On the PR page, navigate to the "Checks" tab, ensure you've selected the "PR" tab on the left, and press "View more details on Azure Pipelines" at the bottom. ![E2E_Debugging_Step_1](../../../../assets/E2E/E2E_Debugging_Step_1.png)
2. Click on the "# published" section. ![E2E_Debugging_Step_2](../../../../assets/E2E/E2E_Debugging_Step_2.png)
3. Here, you have crucial information to help you debug the problem.
- The bottom files (green) are screenshots of the failing tests. The first one from the top (in this case, Svg-Test-Page), is the test failing the whole run. This is the one you're focus should be on.
- The middle file (pink) is the Appium output file. This contains more in-depth information on each test, including possible failures with WebDriverIO or the driver being used.
- The top folder (blue) is the Allure reporter output. In order to generate the report, you must:
1. Download the folder and unzip it
2. Navigate to it's location within your cmd, and type:
- C:\pathToFolder\allure-report\E2E_win32_Dump> `allure open`
![E2E_Debugging_Step_3](../../../../assets/E2E/E2E_Debugging_Step_3.png)

Просмотреть файл

@ -5,6 +5,10 @@ class RadioGroupPage extends BasePage {
get _testPage() {
return By(RADIOGROUP_TESTPAGE);
}
get _pageName() {
return RADIOGROUP_TESTPAGE;
}
}
export default new RadioGroupPage();

Просмотреть файл

@ -5,6 +5,10 @@ class SeparatorTestPage extends BasePage {
get _testPage() {
return By(SEPARATOR_TESTPAGE);
}
get _pageName() {
return SEPARATOR_TESTPAGE;
}
}
export default new SeparatorTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class SvgTestPage extends BasePage {
get _testPage() {
return By(SVG_TESTPAGE);
}
get _pageName() {
return SVG_TESTPAGE;
}
}
export default new SvgTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class TextTestPage extends BasePage {
get _testPage() {
return By(TEXT_TESTPAGE);
}
get _pageName() {
return TEXT_TESTPAGE;
}
}
export default new TextTestPage();

Просмотреть файл

@ -5,6 +5,10 @@ class ThemeTestPage extends BasePage {
get _testPage() {
return By(THEME_TESTPAGE);
}
get _pageName() {
return THEME_TESTPAGE;
}
}
export default new ThemeTestPage();

Просмотреть файл

@ -10,25 +10,27 @@ export class BasePage {
}
// Waits for page to be loaded. Timeout could differ depending on usage.
waitForPageLoaded(timeout?: number) {
waitForPageDisplayed(timeout?: number) {
browser.waitUntil(
() => {
return this.isPageLoaded();
},
this.timeoutForPageLoaded(timeout),
'Timeout Error - The page was not loaded in time.'
timeout ?? this.waitForPageTimeout,
this._pageName + ' did not render correctly. Please see /errorShots of the first failed test for more information.',
1000
);
}
// Actual element on page
get _testPage() {
return By(DUMMY_CHAR);
}
protected timeoutForPageLoaded(currentTimeout?: number) {
if (currentTimeout) return currentTimeout;
return this.waitforPageTimeout;
// Title of page
get _pageName() {
return DUMMY_CHAR;
}
// Default timeout for waitForPageLoaded command in PageObject
private waitforPageTimeout: number = 45000;
}
// Default timeout to wait until page is displayed (10s)
private waitForPageTimeout: number = 10000;
}

Просмотреть файл

@ -1,8 +1,8 @@
import * as React from 'react';
import { findNodeHandle } from 'react-native';
import { Button, IFocusable } from '@fluentui/react-native';
import { View, findNodeHandle, Text } from 'react-native';
import { Button, IFocusable, Separator } from '@fluentui/react-native';
import { Stack } from '@fluentui-react-native/stack';
import { stackStyle } from '../Common/styles';
import { stackStyle, commonTestStyles as commonStyles } from '../Common/styles';
import { BUTTON_TESTPAGE } from './consts';
export const ButtonFocusTest: React.FunctionComponent<{}> = () => {
@ -22,14 +22,15 @@ export const ButtonFocusTest: React.FunctionComponent<{}> = () => {
}, [state, setState]);
return (
<Stack style={stackStyle}>
<Button
testID={BUTTON_TESTPAGE}
content={state.focused ? 'Focused' : 'Not Focused'}
componentRef={buttonRef}
accessibilityLabel="overridden button name"
/>
<Button content="Click to focus" onClick={onFocus} tooltip="button tooltip" />
</Stack>
<View>
<Text style={commonStyles.section} testID={BUTTON_TESTPAGE}>
Basic Buttons
</Text>
<Separator />
<Stack style={stackStyle}>
<Button content={state.focused ? 'Focused' : 'Not Focused'} componentRef={buttonRef} accessibilityLabel="overridden button name" />
<Button content="Click to focus" onClick={onFocus} tooltip="button tooltip" />
</Stack>
</View>
);
};
};

Просмотреть файл

@ -1,7 +1,6 @@
import * as React from 'react';
import { View, TextInput, Text } from 'react-native';
import { Checkbox } from '@fluentui/react-native';
import { Separator } from '@fluentui/react-native';
import { Checkbox, Separator } from '@fluentui/react-native';
import { commonTestStyles as commonStyles } from '../Common/styles';
import { useTheme } from '@uifabricshared/theming-react-native';
import { CHECKBOX_TESTPAGE } from './consts';
@ -65,12 +64,12 @@ export const CheckboxTest: React.FunctionComponent<{}> = () => {
});
const [isCheckedControlled1, setCheckedControlled1] = React.useState(false);
const onChangeControlled1 = React.useCallback(checked => {
const onChangeControlled1 = React.useCallback((checked) => {
setCheckedControlled1(checked);
}, []);
const [isCheckedControlled2, setCheckedControlled2] = React.useState(true);
const onChangeControlled2 = React.useCallback(checked => {
const onChangeControlled2 = React.useCallback((checked) => {
setCheckedControlled2(checked);
}, []);
@ -131,4 +130,4 @@ export const CheckboxTest: React.FunctionComponent<{}> = () => {
/>
</View>
);
};
};

Просмотреть файл

@ -1,9 +1,9 @@
import { IFocusTrapZoneProps, Text, FocusTrapZone, KeyPressEvent } from '@fluentui/react-native';
import { IFocusTrapZoneProps, Text, FocusTrapZone, KeyPressEvent, Separator } from '@fluentui/react-native';
import { Stack } from '@fluentui-react-native/stack';
import { TouchableHighlight, TouchableHighlightProps, View, ViewProps } from 'react-native';
import { useFocusState } from '@fluentui/react-native';
import * as React from 'react';
import { stackStyle } from '../Common/styles';
import { stackStyle, commonTestStyles as commonStyles } from '../Common/styles';
import { FOCUSTRAPZONE_TESTPAGE } from './consts';
const trapZoneStyle: IFocusTrapZoneProps['style'] = {
@ -91,42 +91,48 @@ export const FocusTrapTest: React.FunctionComponent<{}> = () => {
}, [state, setState]);
return (
<View {...{ onKeyDown: onKeyDown }}>
<Stack style={stackStyle} gap={5}>
<ComponentTwiddler label="Press space to render or enter to trap, f to focus" />
<ComponentTwiddler
label={state.ignoreExternalFocusing ? 'ignoreExternalFocusing: true' : 'ignoreExternalFocusing: false'}
onPress={onTwiddleExternalFocusing}
/>
<ComponentTwiddler
label={
state.focusPreviouslyFocusedInnerElement
? 'focusPreviouslyFocusedInnerElement: true'
: 'focusPreviouslyFocusedInnerElement: false'
}
onPress={onTwiddleFocusPrevious}
/>
<ComponentTwiddler
label={state.disableFirstFocus ? 'disableFirstFocus: true' : 'disableFirstFocus: false'}
onPress={onTwiddleFirstFocus}
/>
{state.renderTrapZone && (
<FocusTrapZone
componentRef={ftzRef}
disableFirstFocus={state.disableFirstFocus}
ignoreExternalFocusing={state.ignoreExternalFocusing}
focusPreviouslyFocusedInnerElement={state.focusPreviouslyFocusedInnerElement}
disabled={!state.useTrapZone}
style={state.useTrapZone ? activeTrapZoneStyle : trapZoneStyle}
>
<Text testID={FOCUSTRAPZONE_TESTPAGE}>{state.useTrapZone ? 'Trap Active' : 'Trap Active'}</Text>
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
</FocusTrapZone>
)}
</Stack>
<View>
<Text style={commonStyles.section} testID={FOCUSTRAPZONE_TESTPAGE}>
Basic FocusTrapZone Usage
</Text>
<Separator />
<View {...{ onKeyDown: onKeyDown }}>
<Stack style={stackStyle} gap={5}>
<ComponentTwiddler label="Press space to render or enter to trap, f to focus" />
<ComponentTwiddler
label={state.ignoreExternalFocusing ? 'ignoreExternalFocusing: true' : 'ignoreExternalFocusing: false'}
onPress={onTwiddleExternalFocusing}
/>
<ComponentTwiddler
label={
state.focusPreviouslyFocusedInnerElement
? 'focusPreviouslyFocusedInnerElement: true'
: 'focusPreviouslyFocusedInnerElement: false'
}
onPress={onTwiddleFocusPrevious}
/>
<ComponentTwiddler
label={state.disableFirstFocus ? 'disableFirstFocus: true' : 'disableFirstFocus: false'}
onPress={onTwiddleFirstFocus}
/>
{state.renderTrapZone && (
<FocusTrapZone
componentRef={ftzRef}
disableFirstFocus={state.disableFirstFocus}
ignoreExternalFocusing={state.ignoreExternalFocusing}
focusPreviouslyFocusedInnerElement={state.focusPreviouslyFocusedInnerElement}
disabled={!state.useTrapZone}
style={state.useTrapZone ? activeTrapZoneStyle : trapZoneStyle}
>
<Text>{state.useTrapZone ? 'Trap Active' : 'Trap Active'}</Text>
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
<ComponentTwiddler label="trapped" />
</FocusTrapZone>
)}
</Stack>
</View>
</View>
);
};
};

Просмотреть файл

@ -1,6 +1,6 @@
import * as React from 'react';
import { Alert } from 'react-native';
import { Link } from '@fluentui/react-native';
import { Link, Separator } from '@fluentui/react-native';
import { Stack } from '@fluentui-react-native/stack';
import { stackStyle } from '../Common/styles';
import { LINK_TESTPAGE } from './consts';
@ -16,10 +16,11 @@ export const LinkTest: React.FunctionComponent<{}> = () => {
<Text style={commonStyles.section} testID={LINK_TESTPAGE}>
Link Test Page
</Text>
<Separator />
<Stack style={stackStyle}>
<Link url="https://www.bing.com/" content="Click to navigate." />
<Link onPress={doPress} content="Click to alert." />
</Stack>
</View>
);
};
};

Просмотреть файл

@ -1,9 +1,10 @@
import * as React from 'react';
import { Text, Pressable, IPressableState } from '@fluentui/react-native';
import { Stack } from '@fluentui-react-native/stack';
import { useHoverState, useFocusState, usePressState } from '@fluentui/react-native';
import { useHoverState, useFocusState, usePressState, Separator } from '@fluentui/react-native';
import { Square } from '../Common/Square';
import { Alert, GestureResponderEvent, StyleSheet, View, ViewProps, ViewStyle } from 'react-native';
import { commonTestStyles as commonStyles } from '../Common/styles';
import { PRESSABLE_TESTPAGE } from './consts';
const styles = StyleSheet.create({
@ -54,7 +55,7 @@ const styles = StyleSheet.create({
borderStyle: 'dashed',
borderColor: 'black',
backgroundColor: 'lightgreen'
}
},
});
function renderStyle(state: IPressableState): ViewStyle {
@ -65,29 +66,35 @@ export const PressableTest: React.FunctionComponent<{}> = () => {
const [hoverProps, hoverState] = useHoverState({});
return (
<Stack horizontal gap={5}>
<Square color="blue" />
<Pressable renderStyle={renderStyle}>
<Square />
</Pressable>
<Square color="green" />
<Stack>
<View {...hoverProps as any} style={hoverState.hovered ? styles.dottedBorder : styles.solidBorder}>
<Text>{hoverState.hovered ? 'hovered' : 'not hovered'}</Text>
</View>
<View>
<Text style={commonStyles.section} testID={PRESSABLE_TESTPAGE}>
Pressable Test Page
</Text>
<Separator />
<Stack horizontal gap={5}>
<Square color="blue" />
<Pressable renderStyle={renderStyle}>
<Square />
</Pressable>
<Square color="green" />
<Stack>
<View {...(hoverProps as any)} style={hoverState.hovered ? styles.dottedBorder : styles.solidBorder}>
<Text>{hoverState.hovered ? 'hovered' : 'not hovered'}</Text>
</View>
</Stack>
<Stack>
<Text>Click a component to initially focus and tab to keyboard focus to next component: </Text>
<FocusComponent />
<FocusComponent />
<FocusComponent />
<FocusComponent />
</Stack>
<Stack>
<Text>Press to alert: </Text>
<PressComponent />
</Stack>
</Stack>
<Stack>
<Text testID={PRESSABLE_TESTPAGE}>Click a component to initially focus and tab to keyboard focus to next component: </Text>
<FocusComponent />
<FocusComponent />
<FocusComponent />
<FocusComponent />
</Stack>
<Stack>
<Text>Press to alert: </Text>
<PressComponent />
</Stack>
</Stack>
</View>
);
};
@ -122,4 +129,4 @@ const PressComponent: React.FunctionComponent<ViewProps> = (props: ViewProps) =>
/>
</Stack>
);
};
};

Просмотреть файл

@ -1,6 +1,7 @@
import * as React from 'react';
import { RadioButton, RadioGroup } from '@fluentui/react-native';
import { View } from 'react-native';
import { RadioButton, RadioGroup, Separator } from '@fluentui/react-native';
import { View, Text } from 'react-native';
import { commonTestStyles as commonStyles } from '../Common/styles';
import { RADIOGROUP_TESTPAGE } from './consts';
export const RadioGroupTest: React.FunctionComponent<{}> = () => {
@ -11,12 +12,16 @@ export const RadioGroupTest: React.FunctionComponent<{}> = () => {
return (
<View>
<Text style={commonStyles.section} testID={RADIOGROUP_TESTPAGE}>
Basic RadioGroup Usage
</Text>
<Separator />
<RadioGroup label="This is a test RadioGroup" defaultSelectedKey="A" onChange={onChange}>
<RadioButton content="Option A" buttonKey="A" ariaLabel="Cool" testID={RADIOGROUP_TESTPAGE} />
<RadioButton content="Option A" buttonKey="A" ariaLabel="Cool" />
<RadioButton content="Option B" buttonKey="B" />
<RadioButton content="Option C" buttonKey="C" disabled={true} />
<RadioButton content="Option D" buttonKey="D" />
</RadioGroup>
</View>
);
};
};

Просмотреть файл

@ -1,6 +1,7 @@
import * as React from 'react';
import { View } from 'react-native';
import { Button, Separator, Text } from '@fluentui/react-native';
import { stackStyle, separatorStackStyle } from '../Common/styles';
import { stackStyle, separatorStackStyle, commonTestStyles as commonStyles } from '../Common/styles';
import { Stack } from '@fluentui-react-native/stack';
import { SEPARATOR_TESTPAGE } from './consts';
@ -9,18 +10,24 @@ const RedSeparator = Separator.customize({ tokens: { color: 'red' } });
export const SeparatorTest: React.FunctionComponent<{}> = () => {
return (
<Stack style={stackStyle} gap={5}>
<Stack gap={4} style={separatorStackStyle}>
<Button content="Button4" />
<BlueSeparator vertical />
<Button content="Button5" />
<RedSeparator vertical />
<Button content="Button6" />
<Separator />
</Stack>
<Text testID={SEPARATOR_TESTPAGE}>This is a text element</Text>
<View>
<Text style={commonStyles.section} testID={SEPARATOR_TESTPAGE}>
Separator Test Page
</Text>
<Separator />
<Button content="This button has longer text" />
</Stack>
<Stack style={stackStyle} gap={5}>
<Stack gap={4} style={separatorStackStyle}>
<Button content="Button4" />
<BlueSeparator vertical />
<Button content="Button5" />
<RedSeparator vertical />
<Button content="Button6" />
<Separator />
</Stack>
<Text>This is a text element</Text>
<Separator />
<Button content="This button has longer text" />
</Stack>
</View>
);
};
};

Просмотреть файл

@ -21,7 +21,9 @@
"run-win32": "rex-win32 --bundle index --component FluentTester --windowTitle \"FluentUI Tester\" --basePath ./dist --pluginProps",
"run-win32-web": "rex-win32 --bundle index --component FluentTester --basePath ./dist --useWebDebugger --windowTitle \"FluentUI Tester\" --useLiveReload --pluginProps",
"run-win32-devmain": "rex-win32 --bundle index --component FluentTester --basePath ./dist --useDevMain --windowTitle \"FluentUI Tester\" --pluginProps",
"e2etest": "rimraf reports/* && wdio"
"e2etest": "rimraf reports/* && wdio",
"report": "allure generate allure-results --clean",
"generate-report": "allure generate allure-results --clean && allure open"
},
"dependencies": {
"@fluentui-react-native/tester": "^0.0.1",
@ -41,20 +43,22 @@
"@types/react-native": "^0.62.0",
"@uifabricshared/build-native": "^0.1.1",
"@uifabricshared/eslint-config-rules": "^0.1.1",
"@wdio/appium-service": "^5.0.0",
"@wdio/cli": "^5.22.4",
"@wdio/jasmine-framework": "^5.18.6",
"@wdio/local-runner": "^5.22.4",
"@wdio/spec-reporter": "^5.22.4",
"@wdio/sync": "^5.20.1",
"appium": "1.17.1",
"metro-react-native-babel-preset": "^0.58.0",
"react-native-svg-transformer": "^0.14.3",
"react-test-renderer": "~16.11.0",
"ts-node": "^8.10.1",
"tsconfig-paths": "^3.9.0",
"typescript": "3.8.3",
"webdriverio": "5.22.4"
"allure-commandline": "2.13.0",
"appium": "1.17.1",
"webdriverio": "5.22.4",
"@wdio/appium-service": "^5.0.0",
"@wdio/cli": "^5.22.4",
"@wdio/jasmine-framework": "^5.18.6",
"@wdio/local-runner": "^5.22.4",
"@wdio/spec-reporter": "^5.22.4",
"@wdio/sync": "^5.20.1",
"@wdio/allure-reporter": "^5.22.4"
},
"workspaces": {
"nohoist": [

Просмотреть файл

@ -1,4 +1,6 @@
const path = require('path');
const fs = require('fs');
const rimraf = require('rimraf');
const appPath = path.resolve(path.dirname(require.resolve('@office-iss/rex-win32/rex-win32.js')), 'ReactTest.exe');
const appArgs = 'basePath ' + path.resolve('dist') + ' plugin defaultplugin bundle index component FluentTester';
@ -23,8 +25,8 @@ exports.config = {
deviceName: 'WindowsPC',
app: appPath,
appArguments: appArgs,
appWorkingDir: appDir
}
appWorkingDir: appDir,
},
],
/*
@ -37,26 +39,34 @@ exports.config = {
logLevel: 'info', // Level of logging verbosity: trace | debug | info | warn | error | silent
// If you only want to run your tests until a specific amount of tests have failed use bail (default is 0 - don't bail, run all tests).
bail: 0,
bail: 1,
waitforTimeout: defaultWaitForTimeout, // Default timeout for all waitForXXX commands.
connectionRetryTimeout: defaultConnectionRetryTimeout, // Timeout for any WebDriver request to a driver or grid.
connectionRetryCount: 2, // Maximum count of request retries to the Selenium server.
connectionRetryCount: 1, // Maximum count of request retries to the Selenium server.
port: 4723, // default appium port
services: ['appium'],
appium: {
logPath: './reports/',
args: {
port: '4723'
}
port: '4723',
},
},
framework: 'jasmine',
jasmineNodeOpts: {
defaultTimeoutInterval: jasmineDefaultTimeout
defaultTimeoutInterval: jasmineDefaultTimeout,
},
reporters: ['spec'],
reporters: [
'spec',
[
'allure',
{
outputDir: 'allure-results',
},
],
],
/*
** ===================
@ -95,8 +105,18 @@ exports.config = {
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// beforeSession: function (config, capabilities, specs) {
// },
beforeSession: function (config, capabilities, specs) {
// Delete old screenshots and create empty directory
if (fs.existsSync('./errorShots')) {
rimraf.sync('./errorShots');
}
fs.mkdirSync('./errorShots');
if (fs.existsSync('./allure-results')) {
rimraf.sync('./allure-results');
}
fs.mkdirSync('./allure-results');
},
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
@ -141,11 +161,20 @@ exports.config = {
/**
* Function to be executed after a test (in Mocha/Jasmine).
*/
afterTest: function(test) {
if (test.error !== undefined) {
const name = 'ERROR-' + Date.now();
browser.saveScreenshot('./reports/errorShots/' + name + '.png');
afterTest: function (test) {
// if test passed, ignore, else take and save screenshot.
if (test.passed) {
return;
}
// get current test title and clean it, to use it as file name
const fileName = encodeURIComponent(test.title.replace(/\s+/g, '-'));
// build file path
const filePath = './errorShots/' + fileName + '.png';
// save screenshot
browser.saveScreenshot(filePath);
},
/**
@ -190,7 +219,7 @@ exports.config = {
*/
onComplete: function(exitCode, config, capabilities, results) {
console.log('<<< TESTING FINISHED >>>');
}
},
/**
* Gets executed when a refresh happens.
* @param {String} oldSessionId session ID of the old session
@ -198,4 +227,4 @@ exports.config = {
*/
//onReload: function(oldSessionId, newSessionId) {
//}
};
};

Просмотреть файл

@ -8,7 +8,9 @@
"lint": "eslint .",
"windows": "react-native run-windows",
"bundle": "fluentui-scripts metro --cli",
"e2etest": "rimraf reports/* && wdio"
"e2etest": "rimraf reports/* && wdio",
"report": "allure generate allure-results --clean",
"generate-report": "allure generate allure-results --clean && allure open"
},
"dependencies": {
"@fluentui-react-native/tester": "^0.0.1",
@ -28,18 +30,20 @@
"metro-config": "^0.58.0",
"metro-react-native-babel-preset": "^0.58.0",
"react-test-renderer": "~16.11.0",
"ts-node": "^8.10.1",
"tsconfig-paths": "^3.9.0",
"typescript": "3.8.3",
"@types/jasmine": "3.5.10",
"allure-commandline": "2.13.0",
"appium": "1.17.1",
"webdriverio": "5.22.4",
"@wdio/cli": "^5.22.4",
"@wdio/jasmine-framework": "^5.18.6",
"@wdio/local-runner": "^5.22.4",
"@wdio/sync": "^5.20.1",
"@wdio/spec-reporter": "^5.22.4",
"@wdio/appium-service": "^5.0.0",
"ts-node": "^8.10.1",
"tsconfig-paths": "^3.9.0",
"typescript": "3.8.3",
"@types/jasmine": "3.5.10",
"webdriverio": "5.22.4"
"@wdio/allure-reporter": "^5.22.4"
},
"workspaces": {
"nohoist": [

Просмотреть файл

@ -1,3 +1,6 @@
const fs = require('fs');
const rimraf = require('rimraf');
const defaultWaitForTimeout = 10000;
const defaultConnectionRetryTimeout = 15000;
const jasmineDefaultTimeout = 45000; // 45 seconds for Jasmine test timeout
@ -29,10 +32,10 @@ exports.config = {
logLevel: 'info', // Level of logging verbosity: trace | debug | info | warn | error | silent
// If you only want to run your tests until a specific amount of tests have failed use bail (default is 0 - don't bail, run all tests).
bail: 0,
bail: 1,
waitforTimeout: defaultWaitForTimeout, // Default timeout for all waitForXXX commands.
connectionRetryTimeout: defaultConnectionRetryTimeout, // Timeout for any WebDriver request to a driver or grid.
connectionRetryCount: 2, // Maximum count of request retries to the Selenium server.
connectionRetryCount: 1, // Maximum count of request retries to the Selenium server.
port: 4723, // default appium port
services: ['appium'],
@ -48,7 +51,15 @@ exports.config = {
defaultTimeoutInterval: jasmineDefaultTimeout,
},
reporters: ['spec'],
reporters: [
'spec',
[
'allure',
{
outputDir: 'allure-results',
},
],
],
/*
** ===================
@ -87,8 +98,18 @@ exports.config = {
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// beforeSession: function (config, capabilities, specs) {
// },
beforeSession: function (config, capabilities, specs) {
// Delete old screenshots and create empty directory
if (fs.existsSync('./errorShots')) {
rimraf.sync('./errorShots');
}
fs.mkdirSync('./errorShots');
if (fs.existsSync('./allure-results')) {
rimraf.sync('./allure-results');
}
fs.mkdirSync('./allure-results');
},
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
@ -133,11 +154,20 @@ exports.config = {
/**
* Function to be executed after a test (in Mocha/Jasmine).
*/
afterTest: function(test, context) {
if (test.error !== undefined) {
const name = 'ERROR-' + Date.now();
browser.saveScreenshot('./errorShots/' + name + '.png');
afterTest: function (test, context) {
// if test passed, ignore, else take and save screenshot.
if (test.passed) {
return;
}
// get current test title and clean it, to use it as file name
const fileName = encodeURIComponent(test.title.replace(/\s+/g, '-'));
// build file path
const filePath = './errorShots/' + fileName + '.png';
// save screenshot
browser.saveScreenshot(filePath);
},
/**
@ -190,4 +220,4 @@ exports.config = {
*/
//onReload: function(oldSessionId, newSessionId) {
//}
};
};

Двоичные данные
assets/E2E/E2E_Debugging_Step_1.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 203 KiB

Двоичные данные
assets/E2E/E2E_Debugging_Step_2.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 119 KiB

Двоичные данные
assets/E2E/E2E_Debugging_Step_3.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 57 KiB

Двоичные данные
assets/E2E/E2E_spec_reporter.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 91 KiB

Просмотреть файл

@ -3613,6 +3613,14 @@
resolved "https://registry.yarnpkg.com/@uifabric/prettier-rules/-/prettier-rules-7.0.4.tgz#03082a6cc4b586104738a24892c56e0f1d3df1b6"
integrity sha512-PK60wLAjPrG2l93r1l/VY2BmnC1la0oxIXYBuln5DOAFJcSXMea9RxoNjuqhhqZGZo6aLjO7lW070MeVOvkABQ==
"@wdio/allure-reporter@^5.22.4":
version "5.22.4"
resolved "https://registry.yarnpkg.com/@wdio/allure-reporter/-/allure-reporter-5.22.4.tgz#3d0e3a5d28db5ea37fb1f1c356ed76b130d9aac3"
integrity sha512-iE6LOlrkBSt6JsKxsI2tzaUjUJx+dz9yHVl0yoBW/NYBzxE0sn/0jr8OR+lUV35GdIi90D59MMIrZ/BYS1occA==
dependencies:
"@wdio/reporter" "5.22.4"
allure-js-commons "^1.3.2"
"@wdio/appium-service@^5.0.0":
version "5.18.2"
resolved "https://registry.yarnpkg.com/@wdio/appium-service/-/appium-service-5.18.2.tgz#e6461c939393c3edbb027e69780f0fd2dadff6a4"
@ -4261,6 +4269,23 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
allure-commandline@2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/allure-commandline/-/allure-commandline-2.13.0.tgz#54d77ab8733ded6a8fe2379b60213cd00f5c6425"
integrity sha512-KUByc0nT+vVVsnW/p36R92+FS2o3xVX27p+AW/8T4dlXVh7jx6V0v6+rRBCpK7AhBKD9/T3jsSq14hS0sxp64Q==
allure-js-commons@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/allure-js-commons/-/allure-js-commons-1.3.2.tgz#e1cf0466e36695bb3ced1228f6570eac6c2e9eda"
integrity sha512-FTmoqP36ZjHFT4iLdYamyCFhyj1jqD6BIdiZ5pBlyafDJrFRV76XIXNxwRqbHpSw40o1vHzYi4vGpmREnhnHVw==
dependencies:
file-type "^7.7.1"
fs-extra "^6.0.1"
js2xmlparser "^3.0.0"
mime "^2.3.1"
object-assign "^4.1.1"
uuid "^3.0.0"
anser@^1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760"
@ -9066,6 +9091,11 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
file-type@^7.7.1:
version "7.7.1"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.7.1.tgz#91c2f5edb8ce70688b9b68a90d931bbb6cb21f65"
integrity sha512-bTrKkzzZI6wH+NXhyD3SOXtb2zXTw2SbwI2RxUlRcXVsnN7jNL5hJzVQLYv7FOQhxFkK4XWdAflEaWFpaLLWpQ==
file-type@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18"
@ -9357,6 +9387,15 @@ fs-extra@^1.0.0:
jsonfile "^2.1.0"
klaw "^1.0.0"
fs-extra@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
integrity sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^7.0.1, fs-extra@~7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@ -11447,6 +11486,13 @@ js2xmlparser2@^0.2.0:
resolved "https://registry.yarnpkg.com/js2xmlparser2/-/js2xmlparser2-0.2.0.tgz#a7ca2089b83d02331d631892dd6743864125033f"
integrity sha1-p8ogibg9AjMdYxiS3WdDhkElAz8=
js2xmlparser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-3.0.0.tgz#3fb60eaa089c5440f9319f51760ccd07e2499733"
integrity sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=
dependencies:
xmlcreate "^1.0.1"
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@ -17907,7 +17953,7 @@ uuid-js@^0.7.5:
resolved "https://registry.yarnpkg.com/uuid-js/-/uuid-js-0.7.5.tgz#6c886d02a53d2d40dcf25d91a170b4a7b25b94d0"
integrity sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=
uuid@^3.0.1, uuid@^3.3.2:
uuid@^3.0.0, uuid@^3.0.1, uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
@ -18696,6 +18742,11 @@ xmlbuilder@~11.0.0:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlcreate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f"
integrity sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=
xmldoc@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.2.tgz#6666e029fe25470d599cd30e23ff0d1ed50466d7"