diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 000000000..494b69db1 --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,155 @@ +name: 'Run UI tests' + +on: + push: + branches: + - master + pull_request: + +jobs: + build-summary: + runs-on: [ubuntu-latest] + name: Build summary UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the tests summary on the build summary page of a job + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=BuildSummaryTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + build-results: + runs-on: [ubuntu-latest] + name: Build results UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the detail view of failed unit tests of a build + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=BuildTestResultsTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + details: + runs-on: [ubuntu-latest] + name: Details UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the detail view of a failed JUnit test + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=TestDetailTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + publisher: + runs-on: [ubuntu-latest] + name: Publisher UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Cache local Maven repository + uses: actions/cache@v2.1.7 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the job configuration of the JUnit test results report publisher + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=JobConfigurationTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + job-overview: + runs-on: [ubuntu-latest] + name: Job overview UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the the published results of JUnit tests on the job summary page + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=JobConfigurationTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + class-filter: + runs-on: [ubuntu-latest] + name: Filtered by class UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the unit tests results of a build which are filtered by a class + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=BuildTestResultsByClassTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 + + package-filter: + runs-on: [ubuntu-latest] + name: Filtered by package UI tests + + steps: + - uses: actions/checkout@v2.3.4 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + check-latest: true + cache: 'maven' + - name: Build junit plugin and download dependencies + run: mvn -V -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip + - name: Run UI tests for the unit tests results of a build which are filtered by a package + env: + BROWSER: firefox-container + run: mvn -V -ntp test --file ui-tests/pom.xml -Dtest=BuildTestResultsByPackageTest -Dgpg.skip -Dsurefire.rerunFailingTestsCount=1 diff --git a/Jenkinsfile b/Jenkinsfile index 6b0c48487..013f20b7d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1 +1,4 @@ -buildPlugin(useContainerAgent: true, platforms: ['linux']) +def configurations = [ + [ platform: "docker", jdk: "11" ] +] +buildPlugin(configurations: configurations) diff --git a/plugin/.mvn/extensions.xml b/plugin/.mvn/extensions.xml index 43d628161..a65d82e1b 100644 --- a/plugin/.mvn/extensions.xml +++ b/plugin/.mvn/extensions.xml @@ -2,6 +2,6 @@ io.jenkins.tools.incrementals git-changelist-maven-extension - 1.2 + 1.3 diff --git a/plugin/pom.xml b/plugin/pom.xml index 84cd2a876..53a184c35 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -13,10 +13,10 @@ Allows JUnit-format test results to be published. https://github.com/jenkinsci/junit-plugin - 1.54 + 1.56 -SNAPSHOT jenkinsci/${project.artifactId}-plugin - 2.263.1 + 2.321 8 false @@ -30,7 +30,7 @@ scm:git:git://github.com/${gitHubRepo}.git scm:git:git@github.com:${gitHubRepo}.git https://github.com/${gitHubRepo} - ${scmTag} + ${scmTag} @@ -70,7 +70,7 @@ io.jenkins.plugins github-checks - 1.0.13 + 1.0.16 test @@ -150,7 +150,7 @@ org.jenkins-ci.plugins database - 117.va2009e38b882 + 128.vaa83e142f7f2 test @@ -191,8 +191,8 @@ io.jenkins.tools.bom - bom-2.263.x - 961.vf0c9f6f59827 + bom-2.303.x + 1117.v62a_f6a_01de98 import pom @@ -200,7 +200,7 @@ net.bytebuddy byte-buddy - 1.12.3 + 1.12.8 diff --git a/plugin/src/main/java/hudson/tasks/junit/CaseResult.java b/plugin/src/main/java/hudson/tasks/junit/CaseResult.java index 2b73778be..3a89c1aea 100644 --- a/plugin/src/main/java/hudson/tasks/junit/CaseResult.java +++ b/plugin/src/main/java/hudson/tasks/junit/CaseResult.java @@ -42,8 +42,8 @@ import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.export.Exported; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -653,7 +653,7 @@ public String getFlowNodeId() { return null; } - @Nonnull + @NonNull public List getEnclosingFlowNodeIds() { List enclosing = new ArrayList<>(); if (parent != null) { @@ -662,7 +662,7 @@ public List getEnclosingFlowNodeIds() { return enclosing; } - @Nonnull + @NonNull public List getEnclosingFlowNodeNames() { List enclosing = new ArrayList<>(); if (parent != null) { diff --git a/plugin/src/main/java/hudson/tasks/junit/JUnitResultArchiver.java b/plugin/src/main/java/hudson/tasks/junit/JUnitResultArchiver.java index c167177a6..c52e75d4f 100644 --- a/plugin/src/main/java/hudson/tasks/junit/JUnitResultArchiver.java +++ b/plugin/src/main/java/hudson/tasks/junit/JUnitResultArchiver.java @@ -59,7 +59,7 @@ import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -138,15 +138,15 @@ public JUnitResultArchiver( } @Deprecated - private TestResult parse(String expandedTestResults, Run run, @Nonnull FilePath workspace, Launcher launcher, TaskListener listener) + private TestResult parse(String expandedTestResults, Run run, @NonNull FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { return parse(this, null, expandedTestResults, run, workspace, launcher, listener); } - private static TestResult parse(@Nonnull JUnitTask task, PipelineTestDetails pipelineTestDetails, - String expandedTestResults, Run run, @Nonnull FilePath workspace, + private static TestResult parse(@NonNull JUnitTask task, PipelineTestDetails pipelineTestDetails, + String expandedTestResults, Run run, @NonNull FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { return new JUnitParser(task.isKeepLongStdio(), task.isAllowEmptyResults()) @@ -174,7 +174,7 @@ public void perform(Run build, FilePath workspace, Launcher launcher, /** @deprecated use {@link #parseAndSummarize} instead */ @Deprecated - public static TestResultAction parseAndAttach(@Nonnull JUnitTask task, PipelineTestDetails pipelineTestDetails, + public static TestResultAction parseAndAttach(@NonNull JUnitTask task, PipelineTestDetails pipelineTestDetails, Run build, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { listener.getLogger().println(Messages.JUnitResultArchiver_Recording()); @@ -231,7 +231,7 @@ public static TestResultAction parseAndAttach(@Nonnull JUnitTask task, PipelineT } } - public static TestResultSummary parseAndSummarize(@Nonnull JUnitTask task, PipelineTestDetails pipelineTestDetails, + public static TestResultSummary parseAndSummarize(@NonNull JUnitTask task, PipelineTestDetails pipelineTestDetails, Run build, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { JunitTestResultStorage storage = JunitTestResultStorage.find(); @@ -352,7 +352,7 @@ public double getHealthScaleFactor() { this.healthScaleFactor = Math.max(0.0, healthScaleFactor); } - @Nonnull + @NonNull @Override public List getTestDataPublishers() { return testDataPublishers == null ? Collections.emptyList() : testDataPublishers; @@ -363,7 +363,7 @@ public List getTestDataPublishers() { * * @since 1.2 */ - @DataBoundSetter public final void setTestDataPublishers(@Nonnull List testDataPublishers) { + @DataBoundSetter public final void setTestDataPublishers(@NonNull List testDataPublishers) { this.testDataPublishers = new DescribableList<>(Saveable.NOOP); this.testDataPublishers.addAll(testDataPublishers); } diff --git a/plugin/src/main/java/hudson/tasks/junit/SuiteResult.java b/plugin/src/main/java/hudson/tasks/junit/SuiteResult.java index 83c5d6d1a..8fc5d2e37 100644 --- a/plugin/src/main/java/hudson/tasks/junit/SuiteResult.java +++ b/plugin/src/main/java/hudson/tasks/junit/SuiteResult.java @@ -35,8 +35,8 @@ import org.kohsuke.stapler.export.ExportedBean; import org.xml.sax.SAXException; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -331,7 +331,7 @@ public String getNodeId() { * @since 1.22 */ @Exported(visibility=9) - @Nonnull + @NonNull public List getEnclosingBlocks() { if (enclosingBlocks != null) { return Collections.unmodifiableList(enclosingBlocks); @@ -346,7 +346,7 @@ public List getEnclosingBlocks() { * @since 1.22 */ @Exported(visibility=9) - @Nonnull + @NonNull public List getEnclosingBlockNames() { if (enclosingBlockNames != null) { return Collections.unmodifiableList(enclosingBlockNames); diff --git a/plugin/src/main/java/hudson/tasks/junit/TestDataPublisher.java b/plugin/src/main/java/hudson/tasks/junit/TestDataPublisher.java index 818ddebd8..0d2e7f308 100644 --- a/plugin/src/main/java/hudson/tasks/junit/TestDataPublisher.java +++ b/plugin/src/main/java/hudson/tasks/junit/TestDataPublisher.java @@ -33,7 +33,7 @@ import jenkins.model.Jenkins; import java.io.IOException; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Contributes {@link TestAction}s to test results. @@ -69,7 +69,7 @@ public abstract class TestDataPublisher extends AbstractDescribableImpl run, @Nonnull FilePath workspace, Launcher launcher, + Run run, @NonNull FilePath workspace, Launcher launcher, TaskListener listener, TestResult testResult) throws IOException, InterruptedException { if (run instanceof AbstractBuild && listener instanceof BuildListener) { return getTestData((AbstractBuild) run, launcher, (BuildListener) listener, testResult); diff --git a/plugin/src/main/java/hudson/tasks/junit/TestResult.java b/plugin/src/main/java/hudson/tasks/junit/TestResult.java index 034db7cae..63ccecb6d 100644 --- a/plugin/src/main/java/hudson/tasks/junit/TestResult.java +++ b/plugin/src/main/java/hudson/tasks/junit/TestResult.java @@ -46,7 +46,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import javax.annotation.CheckForNull; +import edu.umd.cs.findbugs.annotations.CheckForNull; import org.apache.tools.ant.DirectoryScanner; import org.dom4j.DocumentException; @@ -54,7 +54,7 @@ import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Root of all the test results for one build. @@ -216,32 +216,11 @@ public void parse(long buildTime, File baseDir, String[] reportFiles) */ public void parse(long buildTime, File baseDir, PipelineTestDetails pipelineTestDetails, String[] reportFiles) throws IOException { - boolean parsed=false; for (String value : reportFiles) { File reportFile = new File(baseDir, value); // only count files that were actually updated during this build - if (buildTime-3000/*error margin*/ <= reportFile.lastModified()) { - parsePossiblyEmpty(reportFile, pipelineTestDetails); - parsed = true; - } - } - - if(!parsed) { - long localTime = System.currentTimeMillis(); - if(localTime < buildTime-1000) /*margin*/ - // build time is in the the future. clock on this agent must be running behind - throw new AbortException( - "Clock on this agent is out of sync with the controller, and therefore \n" + - "I can't figure out what test results are new and what are old.\n" + - "Please keep the agent clock in sync with the controller."); - - File f = new File(baseDir,reportFiles[0]); - throw new AbortException( - String.format( - "Test reports were found but none of them are new. Did leafNodes run? %n"+ - "For example, %s is %s old%n", f, - Util.getTimeSpanString(buildTime-f.lastModified()))); + parsePossiblyEmpty(reportFile, pipelineTestDetails); } } @@ -256,7 +235,7 @@ public hudson.tasks.test.TestResult getPreviousResult() { @Deprecated public void parse(long buildTime, Iterable reportFiles) throws IOException { - parse(buildTime, reportFiles, null); + parse(reportFiles, null); } /** @@ -268,35 +247,26 @@ public void parse(long buildTime, Iterable reportFiles) throws IOException * * @throws IOException if an error occurs. * @since 1.22 + * @deprecated use {@link #parse(Iterable, PipelineTestDetails)} */ + @Deprecated public void parse(long buildTime, Iterable reportFiles, PipelineTestDetails pipelineTestDetails) throws IOException { - boolean parsed=false; + parse(reportFiles, pipelineTestDetails); + } + /** + * Collect reports from the given report files + * + * @param reportFiles Report files. + * @param pipelineTestDetails A {@link PipelineTestDetails} instance containing Pipeline-related additional arguments. + * + * @throws IOException if an error occurs. + */ + public void parse(Iterable reportFiles, PipelineTestDetails pipelineTestDetails) throws IOException { for (File reportFile : reportFiles) { // only count files that were actually updated during this build - if (buildTime-3000/*error margin*/ <= reportFile.lastModified()) { - parsePossiblyEmpty(reportFile, pipelineTestDetails); - parsed = true; - } + parsePossiblyEmpty(reportFile, pipelineTestDetails); } - - if(!parsed) { - long localTime = System.currentTimeMillis(); - if(localTime < buildTime-1000) /*margin*/ - // build time is in the the future. clock on this agent must be running behind - throw new AbortException( - "Clock on this agent is out of sync with the controller, and therefore \n" + - "I can't figure out what test results are new and what are old.\n" + - "Please keep the agent clock in sync with the controller."); - - File f = reportFiles.iterator().next(); - throw new AbortException( - String.format( - "Test reports were found but none of them are new. Did leafNodes run? %n"+ - "For example, %s is %s old%n", f, - Util.getTimeSpanString(buildTime-f.lastModified()))); - } - } private void parsePossiblyEmpty(File reportFile, PipelineTestDetails pipelineTestDetails) throws IOException { @@ -723,13 +693,13 @@ public SuiteResult getSuite(String name) { return suitesByName.get(name); } - @Nonnull - public TestResult getResultByNode(@Nonnull String nodeId) { + @NonNull + public TestResult getResultByNode(@NonNull String nodeId) { return getResultByNodes(Collections.singletonList(nodeId)); } - @Nonnull - public TestResult getResultByNodes(@Nonnull List nodeIds) { + @NonNull + public TestResult getResultByNodes(@NonNull List nodeIds) { if (impl != null) { return impl.getResultByNodes(nodeIds); } @@ -895,8 +865,8 @@ private void addSuiteByNode(SuiteResult s) { } } - @Nonnull - public TestResult getResultForPipelineBlock(@Nonnull String blockId) { + @NonNull + public TestResult getResultForPipelineBlock(@NonNull String blockId) { PipelineBlockWithTests block = getPipelineBlockWithTests(blockId); if (block != null) { return (TestResult)blockToTestResult(block, this); @@ -909,8 +879,8 @@ public TestResult getResultForPipelineBlock(@Nonnull String blockId) { * Get an aggregated {@link TestResult} for all test results in a {@link PipelineBlockWithTests} and any children it may have. */ @Override - @Nonnull - public TabulatedResult blockToTestResult(@Nonnull PipelineBlockWithTests block, @Nonnull TabulatedResult fullResult) { + @NonNull + public TabulatedResult blockToTestResult(@NonNull PipelineBlockWithTests block, @NonNull TabulatedResult fullResult) { TestResult result = new TestResult(); for (PipelineBlockWithTests child : block.getChildBlocks().values()) { TabulatedResult childResult = blockToTestResult(child, fullResult); diff --git a/plugin/src/main/java/hudson/tasks/junit/TestResultAction.java b/plugin/src/main/java/hudson/tasks/junit/TestResultAction.java index 261066f61..3d3da4c03 100644 --- a/plugin/src/main/java/hudson/tasks/junit/TestResultAction.java +++ b/plugin/src/main/java/hudson/tasks/junit/TestResultAction.java @@ -51,7 +51,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.Nullable; import jenkins.tasks.SimpleBuildStep; /** diff --git a/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStep.java b/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStep.java index c640359ce..83a800245 100644 --- a/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStep.java +++ b/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStep.java @@ -8,7 +8,6 @@ import hudson.model.Saveable; import hudson.model.TaskListener; import hudson.tasks.junit.JUnitTask; -import hudson.tasks.junit.Messages; import hudson.tasks.junit.TestDataPublisher; import hudson.util.DescribableList; import hudson.util.FormValidation; @@ -22,7 +21,7 @@ import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -85,7 +84,7 @@ public final void setHealthScaleFactor(double healthScaleFactor) { this.healthScaleFactor = Math.max(0.0, healthScaleFactor); } - @Nonnull + @NonNull @Override public List getTestDataPublishers() { return testDataPublishers == null ? Collections.emptyList() : testDataPublishers; @@ -96,7 +95,7 @@ public List getTestDataPublishers() { * * @since 1.2 */ - @DataBoundSetter public final void setTestDataPublishers(@Nonnull List testDataPublishers) { + @DataBoundSetter public final void setTestDataPublishers(@NonNull List testDataPublishers) { this.testDataPublishers = new DescribableList<>(Saveable.NOOP); this.testDataPublishers.addAll(testDataPublishers); } @@ -178,7 +177,7 @@ public String getFunctionName() { } @Override - @Nonnull + @NonNull public String getDisplayName() { return "Archive JUnit-formatted test results"; } @@ -190,15 +189,5 @@ public Set> getRequiredContext() { return Collections.unmodifiableSet(context); } - public FormValidation doCheckHealthScaleFactor(@QueryParameter double value) { - if (value < 1e-7) return FormValidation.warning("Test health reporting disabled"); - return FormValidation.ok(Messages.JUnitResultArchiver_HealthScaleFactorAnalysis( - 1, - (int) (100.0 - Math.max(0.0, Math.min(100.0, 1 * value))), - 5, - (int) (100.0 - Math.max(0.0, Math.min(100.0, 5 * value))) - )); - } - } } diff --git a/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStepExecution.java b/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStepExecution.java index a5b3a9d46..bd29b8db1 100644 --- a/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStepExecution.java +++ b/plugin/src/main/java/hudson/tasks/junit/pipeline/JUnitResultsStepExecution.java @@ -12,7 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; import io.jenkins.plugins.checks.steps.ChecksInfo; import org.jenkinsci.plugins.workflow.actions.LabelAction; @@ -30,7 +30,7 @@ public class JUnitResultsStepExecution extends SynchronousNonBlockingStepExecuti private transient final JUnitResultsStep step; - public JUnitResultsStepExecution(@Nonnull JUnitResultsStep step, StepContext context) { + public JUnitResultsStepExecution(@NonNull JUnitResultsStep step, StepContext context) { super(context); this.step = step; } @@ -84,7 +84,7 @@ protected TestResultSummary run() throws Exception { * @param node A flownode. * @return A nonnull, possibly empty list of stage/parallel branch start nodes, innermost first. */ - @Nonnull + @NonNull public static List getEnclosingStagesAndParallels(FlowNode node) { List enclosingBlocks = new ArrayList<>(); for (FlowNode enclosing : node.getEnclosingBlocks()) { @@ -99,7 +99,7 @@ public static List getEnclosingStagesAndParallels(FlowNode node) { return enclosingBlocks; } - private static boolean isStageNode(@Nonnull FlowNode node) { + private static boolean isStageNode(@NonNull FlowNode node) { if (node instanceof StepNode) { StepDescriptor d = ((StepNode) node).getDescriptor(); return d != null && d.getFunctionName().equals("stage"); @@ -108,8 +108,8 @@ private static boolean isStageNode(@Nonnull FlowNode node) { } } - @Nonnull - public static List getEnclosingBlockIds(@Nonnull List nodes) { + @NonNull + public static List getEnclosingBlockIds(@NonNull List nodes) { List ids = new ArrayList<>(); for (FlowNode n : nodes) { ids.add(n.getId()); @@ -117,8 +117,8 @@ public static List getEnclosingBlockIds(@Nonnull List nodes) { return ids; } - @Nonnull - public static List getEnclosingBlockNames(@Nonnull List nodes) { + @NonNull + public static List getEnclosingBlockNames(@NonNull List nodes) { List names = new ArrayList<>(); for (FlowNode n : nodes) { ThreadNameAction threadNameAction = n.getPersistentAction(ThreadNameAction.class); diff --git a/plugin/src/main/java/hudson/tasks/test/AbstractTestResultAction.java b/plugin/src/main/java/hudson/tasks/test/AbstractTestResultAction.java index 37cc766d2..0c2f416bc 100644 --- a/plugin/src/main/java/hudson/tasks/test/AbstractTestResultAction.java +++ b/plugin/src/main/java/hudson/tasks/test/AbstractTestResultAction.java @@ -38,7 +38,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.model.RunAction2; import jenkins.model.lazy.LazyBuildMixIn; import org.jfree.chart.ChartFactory; @@ -278,7 +278,7 @@ public List getFailedTests() { * @return List of passed tests from associated test result. * @since 1.10 */ - @Nonnull + @NonNull public List getPassedTests() { return Collections.emptyList(); } @@ -289,7 +289,7 @@ public List getPassedTests() { * @return List of skipped tests from associated test result. * @since 1.10 */ - @Nonnull + @NonNull public List getSkippedTests() { return Collections.emptyList(); } diff --git a/plugin/src/main/java/hudson/tasks/test/AggregatedTestResultPublisher.java b/plugin/src/main/java/hudson/tasks/test/AggregatedTestResultPublisher.java index 4733a3cc6..d1ba5d1bc 100644 --- a/plugin/src/main/java/hudson/tasks/test/AggregatedTestResultPublisher.java +++ b/plugin/src/main/java/hudson/tasks/test/AggregatedTestResultPublisher.java @@ -58,7 +58,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.annotation.CheckForNull; +import edu.umd.cs.findbugs.annotations.CheckForNull; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -375,7 +375,7 @@ public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryPar @Override public AggregatedTestResultPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { // Starting in 1.640, Descriptor#newInstance is - // newInstance(@CheckForNull StaplerRequest req, @Nonnull JSONObject formData) + // newInstance(@CheckForNull StaplerRequest req, @NonNull JSONObject formData) if (formData == null) { // Should not happen. See above throw new AssertionError("Null parameters to Descriptor#newInstance"); diff --git a/plugin/src/main/java/hudson/tasks/test/PipelineBlockWithTests.java b/plugin/src/main/java/hudson/tasks/test/PipelineBlockWithTests.java index 8f76ec925..57d562548 100644 --- a/plugin/src/main/java/hudson/tasks/test/PipelineBlockWithTests.java +++ b/plugin/src/main/java/hudson/tasks/test/PipelineBlockWithTests.java @@ -1,6 +1,6 @@ package hudson.tasks.test; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.Serializable; import java.util.Map; import java.util.Set; @@ -12,34 +12,34 @@ public class PipelineBlockWithTests implements Serializable { private final Map childBlocks = new TreeMap<>(); private final Set leafNodes = new TreeSet<>(); - public PipelineBlockWithTests(@Nonnull String blockId) { + public PipelineBlockWithTests(@NonNull String blockId) { this.blockId = blockId; } - @Nonnull + @NonNull public String getBlockId() { return blockId; } - @Nonnull + @NonNull public Map getChildBlocks() { return childBlocks; } - @Nonnull + @NonNull public Set getLeafNodes() { return leafNodes; } - public void addChildBlock(@Nonnull PipelineBlockWithTests child) { + public void addChildBlock(@NonNull PipelineBlockWithTests child) { childBlocks.put(child.getBlockId(), child); } - public void addLeafNode(@Nonnull String leafNode) { + public void addLeafNode(@NonNull String leafNode) { leafNodes.add(leafNode); } - public void merge(@Nonnull PipelineBlockWithTests toMerge) { + public void merge(@NonNull PipelineBlockWithTests toMerge) { if (toMerge.getBlockId().equals(blockId)) { if (!this.equals(toMerge)) { for (String childId : toMerge.getChildBlocks().keySet()) { @@ -82,7 +82,7 @@ public int hashCode() { return result; } - @Nonnull + @NonNull public Set nodesWithTests() { Set nodes = new TreeSet<>(); diff --git a/plugin/src/main/java/hudson/tasks/test/PipelineTestDetails.java b/plugin/src/main/java/hudson/tasks/test/PipelineTestDetails.java index 1887017cc..68fe103d2 100644 --- a/plugin/src/main/java/hudson/tasks/test/PipelineTestDetails.java +++ b/plugin/src/main/java/hudson/tasks/test/PipelineTestDetails.java @@ -1,7 +1,7 @@ package hudson.tasks.test; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -19,25 +19,25 @@ public String getNodeId() { return nodeId; } - public void setNodeId(@Nonnull String nodeId) { + public void setNodeId(@NonNull String nodeId) { this.nodeId = nodeId; } - @Nonnull + @NonNull public List getEnclosingBlocks() { return enclosingBlocks; } - public void setEnclosingBlocks(@Nonnull List enclosingBlocks) { + public void setEnclosingBlocks(@NonNull List enclosingBlocks) { this.enclosingBlocks.addAll(enclosingBlocks); } - @Nonnull + @NonNull public List getEnclosingBlockNames() { return enclosingBlockNames; } - public void setEnclosingBlockNames(@Nonnull List enclosingBlockNames) { + public void setEnclosingBlockNames(@NonNull List enclosingBlockNames) { this.enclosingBlockNames.addAll(enclosingBlockNames); } diff --git a/plugin/src/main/java/hudson/tasks/test/TabulatedResult.java b/plugin/src/main/java/hudson/tasks/test/TabulatedResult.java index ab3b550ae..e4fda502f 100644 --- a/plugin/src/main/java/hudson/tasks/test/TabulatedResult.java +++ b/plugin/src/main/java/hudson/tasks/test/TabulatedResult.java @@ -23,8 +23,8 @@ */ package hudson.tasks.test; -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Collection; import java.util.List; import java.util.Map; @@ -71,14 +71,14 @@ public boolean hasMultipleBlocks() { } @CheckForNull - public PipelineBlockWithTests getPipelineBlockWithTests(@Nonnull String blockId) { + public PipelineBlockWithTests getPipelineBlockWithTests(@NonNull String blockId) { if (testsByBlock.containsKey(blockId)) { return testsByBlock.get(blockId); } return null; } - protected final void populateBlocks(@Nonnull List innermostFirst, @Nonnull String nodeId, + protected final void populateBlocks(@NonNull List innermostFirst, @NonNull String nodeId, @CheckForNull PipelineBlockWithTests nested) { if (innermostFirst.isEmpty()) { if (nested != null) { @@ -100,7 +100,7 @@ protected final void populateBlocks(@Nonnull List innermostFirst, @Nonnu } } - private void addOrMergeBlock(@Nonnull PipelineBlockWithTests b) { + private void addOrMergeBlock(@NonNull PipelineBlockWithTests b) { if (testsByBlock.containsKey(b.getBlockId())) { testsByBlock.get(b.getBlockId()).merge(b); } else { @@ -113,8 +113,8 @@ private void addOrMergeBlock(@Nonnull PipelineBlockWithTests b) { * * Default implementation just returns the original. */ - @Nonnull - public TabulatedResult blockToTestResult(@Nonnull PipelineBlockWithTests block, @Nonnull TabulatedResult fullResult) { + @NonNull + public TabulatedResult blockToTestResult(@NonNull PipelineBlockWithTests block, @NonNull TabulatedResult fullResult) { return fullResult; } diff --git a/plugin/src/main/java/hudson/tasks/test/TestResultParser.java b/plugin/src/main/java/hudson/tasks/test/TestResultParser.java index c3d380289..8d90229c1 100644 --- a/plugin/src/main/java/hudson/tasks/test/TestResultParser.java +++ b/plugin/src/main/java/hudson/tasks/test/TestResultParser.java @@ -34,7 +34,7 @@ import hudson.model.TaskListener; import java.io.IOException; -import javax.annotation.Nonnull; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Parses test result files and builds in-memory representation of it as {@link TestResult}. @@ -84,7 +84,7 @@ public static ExtensionList all() { @Deprecated public TestResult parseResult(String testResultLocations, - Run run, @Nonnull FilePath workspace, Launcher launcher, + Run run, @NonNull FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { return parseResult(testResultLocations, run, null, workspace, launcher, listener); @@ -135,7 +135,7 @@ public TestResult parseResult(String testResultLocations, */ public TestResult parseResult(String testResultLocations, Run run, PipelineTestDetails pipelineTestDetails, - @Nonnull FilePath workspace, Launcher launcher, TaskListener listener) + @NonNull FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { if (run instanceof AbstractBuild) { return parse(testResultLocations, (AbstractBuild) run, launcher, listener); diff --git a/plugin/src/main/resources/hudson/tasks/junit/CaseResult/list.jelly b/plugin/src/main/resources/hudson/tasks/junit/CaseResult/list.jelly index bcaffe4d9..549844fbb 100644 --- a/plugin/src/main/resources/hudson/tasks/junit/CaseResult/list.jelly +++ b/plugin/src/main/resources/hudson/tasks/junit/CaseResult/list.jelly @@ -26,22 +26,23 @@ THE SOFTWARE. Trend of test execution over time. --> - - - - - - - - + +
${%Build}${%Test Description}${%Test Duration}${%Test Result}
+ + + + + + + +
${%Build}${%Test Description}${%Test Duration}${%Test Result}
- ${b.fullDisplayName} + ${b.fullDisplayName} diff --git a/plugin/src/main/resources/hudson/tasks/junit/ClassResult/body.jelly b/plugin/src/main/resources/hudson/tasks/junit/ClassResult/body.jelly index c99d2774d..5e74fc38a 100644 --- a/plugin/src/main/resources/hudson/tasks/junit/ClassResult/body.jelly +++ b/plugin/src/main/resources/hudson/tasks/junit/ClassResult/body.jelly @@ -23,20 +23,22 @@ THE SOFTWARE. --> - +

${%All Tests}

- - - - - - +
${%Test name}${%Duration}${%Status}
+ + + + + + +
${%Test name}${%Duration}${%Status}
- + diff --git a/plugin/src/main/resources/hudson/tasks/junit/ClassResult/list.jelly b/plugin/src/main/resources/hudson/tasks/junit/ClassResult/list.jelly index 661524be6..5f0a56d21 100644 --- a/plugin/src/main/resources/hudson/tasks/junit/ClassResult/list.jelly +++ b/plugin/src/main/resources/hudson/tasks/junit/ClassResult/list.jelly @@ -23,24 +23,25 @@ THE SOFTWARE. --> - - - - - - - - - - + +
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
+ + + + + + + + + +
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
- ${b.fullDisplayName} + ${b.fullDisplayName} diff --git a/plugin/src/main/resources/hudson/tasks/junit/History/index.jelly b/plugin/src/main/resources/hudson/tasks/junit/History/index.jelly index 1728246c7..b46dcce49 100644 --- a/plugin/src/main/resources/hudson/tasks/junit/History/index.jelly +++ b/plugin/src/main/resources/hudson/tasks/junit/History/index.jelly @@ -70,23 +70,26 @@ THE SOFTWARE. - - - - - - - - - - - +
+
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
+ + + + + + + + + + + + diff --git a/plugin/src/main/resources/hudson/tasks/test/AggregatedTestResultPublisher/TestResultAction/index.jelly b/plugin/src/main/resources/hudson/tasks/test/AggregatedTestResultPublisher/TestResultAction/index.jelly index db10c49c2..7e1faad7c 100644 --- a/plugin/src/main/resources/hudson/tasks/test/AggregatedTestResultPublisher/TestResultAction/index.jelly +++ b/plugin/src/main/resources/hudson/tasks/test/AggregatedTestResultPublisher/TestResultAction/index.jelly @@ -35,12 +35,14 @@ THE SOFTWARE.

${%Drill Down}

-
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
- ${item.fullDisplayName} + ${item.fullDisplayName}
- - - - - +
${%Test}${%Fail}${%Total}
+ + + + + + + @@ -61,7 +63,7 @@ THE SOFTWARE.
${%Test}${%Fail}${%Total}
- ${i.fullDisplayName} + ${i.fullDisplayName} (${%test result not available}) @@ -75,7 +77,7 @@ THE SOFTWARE.
- ${i.fullDisplayName} + ${i.fullDisplayName} (${%last successful job is not fingerprinted}) @@ -91,4 +93,4 @@ THE SOFTWARE. - \ No newline at end of file + diff --git a/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/body.jelly b/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/body.jelly index b99f42b29..91dfd81cd 100644 --- a/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/body.jelly +++ b/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/body.jelly @@ -24,15 +24,17 @@ THE SOFTWARE. --> - +

${%All Failed Tests}

- - - - - - +
${%Test Name}${%Duration}${%Age}
+ + + + + + + @@ -40,7 +42,7 @@ THE SOFTWARE. ${f.durationString} @@ -49,26 +51,28 @@ THE SOFTWARE.

${%All Tests}

-
${%Test Name}${%Duration}${%Age}
- ${f.age} + ${f.age}
- - - - - - - - - - - - +
${it.childTitle}${%Duration}${%Fail}(${%diff})${%Skip}(${%diff})${%Pass}(${%diff})${%Total}(${%diff})
+ + + + + + + + + + + + + +
${it.childTitle}${%Duration}${%Fail}(${%diff})${%Skip}(${%diff})${%Pass}(${%diff})${%Total}(${%diff})
- + diff --git a/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/list.jelly b/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/list.jelly index 76f58239c..848b584ce 100644 --- a/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/list.jelly +++ b/plugin/src/main/resources/hudson/tasks/test/MetaTabulatedResult/list.jelly @@ -23,24 +23,25 @@ THE SOFTWARE. --> - - - - - - - - - - + +
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
+ + + + + + + + + +
${%Build}${%Description}${%Duration}${%Fail}${%Skip}${%Total}
- ${b.fullDisplayName} + ${b.fullDisplayName} diff --git a/plugin/src/main/resources/lib/hudson/test/aggregated-failed-tests.jelly b/plugin/src/main/resources/lib/hudson/test/aggregated-failed-tests.jelly index c1e1d23ee..4e73ae6ff 100644 --- a/plugin/src/main/resources/lib/hudson/test/aggregated-failed-tests.jelly +++ b/plugin/src/main/resources/lib/hudson/test/aggregated-failed-tests.jelly @@ -23,7 +23,7 @@ THE SOFTWARE. --> - + Display links to failed test from all child reports. @since 1.538 @@ -41,15 +41,17 @@ THE SOFTWARE.

- ${report.child.project.name} + ${report.child.project.name}

- - - - - - +
Test NameDurationAge
+ + + + + + + diff --git a/plugin/src/main/resources/lib/hudson/test/failed-test.jelly b/plugin/src/main/resources/lib/hudson/test/failed-test.jelly index 78e95215e..862866e30 100644 --- a/plugin/src/main/resources/lib/hudson/test/failed-test.jelly +++ b/plugin/src/main/resources/lib/hudson/test/failed-test.jelly @@ -91,7 +91,7 @@ THE SOFTWARE. - + diff --git a/plugin/src/test/java/hudson/tasks/junit/pipeline/JUnitResultsStepTest.java b/plugin/src/test/java/hudson/tasks/junit/pipeline/JUnitResultsStepTest.java index 146d0e5bb..8f09bf970 100644 --- a/plugin/src/test/java/hudson/tasks/junit/pipeline/JUnitResultsStepTest.java +++ b/plugin/src/test/java/hudson/tasks/junit/pipeline/JUnitResultsStepTest.java @@ -41,7 +41,7 @@ import org.jvnet.hudson.test.TestExtension; import org.kohsuke.stapler.DataBoundConstructor; -import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; diff --git a/plugin/src/test/resources/hudson/tasks/test/TrivialTestResult/body.jelly b/plugin/src/test/resources/hudson/tasks/test/TrivialTestResult/body.jelly index 437283d5b..0b1e20c58 100644 --- a/plugin/src/test/resources/hudson/tasks/test/TrivialTestResult/body.jelly +++ b/plugin/src/test/resources/hudson/tasks/test/TrivialTestResult/body.jelly @@ -23,16 +23,16 @@ THE SOFTWARE. --> - +

TrivialTestResult body.jelly

${%All Failed Tests}

-
Test NameDurationAge
+
- - - + + + @@ -65,19 +65,21 @@ THE SOFTWARE.

${%All Tests}

-
${%Test Name}${%Duration}${%Age}${%Test Name}${%Duration}${%Age}
- - - - - - - - - - - - +
${it.childTitle}${%Duration}${%Fail}(${%diff})${%Skip}(${%diff})${%Pass}(${%diff})${%Total}(${%diff})
+ + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 926a29a44..262451cdf 100644 --- a/pom.xml +++ b/pom.xml @@ -46,4 +46,4 @@ - \ No newline at end of file + diff --git a/ui-tests/pom.xml b/ui-tests/pom.xml index 2ad3d425d..f2167781f 100644 --- a/ui-tests/pom.xml +++ b/ui-tests/pom.xml @@ -5,7 +5,7 @@ edu.hm.hafner codingstyle-pom - 2.13.1 + 2.16.0 @@ -13,10 +13,10 @@ junit-ui-tests jar UNVERSIONED - UI Tests of Jenkins Plugin + UI Tests of JUnit Plugin - 2.303.3 + 2.333 2.3 2.28.0 @@ -26,18 +26,16 @@ org.jenkins-ci.main jenkins-core - 2.324 + ${jenkins.version} - - org.jenkins-ci acceptance-test-harness - 1.106 + 1.108 org.apache.httpcomponents @@ -149,13 +147,13 @@ assertj-assertions-generator-maven-plugin - io.jenkins.plugins.analysis.warnings + io.jenkins.plugins.analysis.junit .*Test .*Table - io.jenkins.plugins.analysis.warnings + io.jenkins.plugins.analysis.junit @@ -174,7 +172,7 @@ ../plugin/target/ - warnings-ng.hpi + junit.hpi false @@ -214,6 +212,17 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + junit.ui.tests + + + + - \ No newline at end of file + diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/BuildChartEntry.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/BuildChartEntry.java new file mode 100644 index 000000000..66739d39c --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/BuildChartEntry.java @@ -0,0 +1,65 @@ +package io.jenkins.plugins.analysis.junit; + +/** + * The entry of the build chart, located on the project overview. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class BuildChartEntry { + + final int buildId; + + final int numberOfSkippedTests; + + final int numberOfFailedTests; + + final int numberOfPassedTests; + + /** + * Custom constructor. Creates object. + * @param buildId the build id + * @param numberOfSkippedTests the number of skipped tests + * @param numberOfFailedTests the number of failed tests + * @param numberOfPassedTests the number of passed tests + */ + public BuildChartEntry(final int buildId, final int numberOfSkippedTests, final int numberOfFailedTests, + final int numberOfPassedTests) { + this.buildId = buildId; + this.numberOfSkippedTests = numberOfSkippedTests; + this.numberOfFailedTests = numberOfFailedTests; + this.numberOfPassedTests = numberOfPassedTests; + } + + /** + * Gets the build id. + * @return the build id + */ + public int getBuildId() { + return buildId; + } + + /** + * Gets the number of skipped tests. + * @return the number of skipped tests + */ + public int getNumberOfSkippedTests() { + return numberOfSkippedTests; + } + + /** + * Gets the number of failed tests. + * @return the number of failed tests + */ + public int getNumberOfFailedTests() { + return numberOfFailedTests; + } + + /** + * Gets the number of passed tests. + * @return the number of passed tests + */ + public int getNumberOfPassedTests() { + return numberOfPassedTests; + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitBuildSummary.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitBuildSummary.java new file mode 100644 index 000000000..e15162b70 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitBuildSummary.java @@ -0,0 +1,143 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +import io.jenkins.plugins.analysis.junit.testresults.BuildTestResults; + +/** + * {@link PageObject} representing the JUnit summary on the build page of a job. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class JUnitBuildSummary extends PageObject { + + private final WebElement summaryIcon; + private final WebElement summaryContent; + + private final WebElement titleLink; + private final List failedTestLinks; + + /** + * Creates a new page object representing the JUnit summary on the build page of a job. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public JUnitBuildSummary(final Build parent) { + super(parent, parent.url("testReport")); + + WebElement table = getElement(By.cssSelector("#main-panel table")); + List tableEntries = table.findElements(By.cssSelector("tbody tr")); + + WebElement junitBuildSummaryTableEntry = tableEntries.stream() + .filter(trElement -> findIconInTableEntry(trElement).isPresent()) + .filter(trElement -> findContentInTableEntry(trElement).isPresent()) + .findAny() + .orElseThrow(() -> new NoSuchElementException("junit build summary table")); + + summaryIcon = findIconInTableEntry(junitBuildSummaryTableEntry).get(); + summaryContent = findContentInTableEntry(junitBuildSummaryTableEntry).get(); + + titleLink = summaryContent.findElement(By.cssSelector("a")); + failedTestLinks = summaryContent.findElements(By.cssSelector("ul li a")); + } + + private Optional findIconInTableEntry(final WebElement tableEntry) { + return findOptionalElement(tableEntry, By.cssSelector("td img.icon-clipboard.icon-xlg")); + } + + private Optional findContentInTableEntry(final WebElement tableEntry) { + List foundElements = tableEntry.findElements(By.cssSelector("td")); + return foundElements.stream() + .filter(foundElement -> findOptionalElement(foundElement, By.cssSelector("a")).isPresent() + && findOptionalElement(foundElement, By.cssSelector("a")).get().getText().equals("Test Result")) + .findFirst(); + } + + private Optional findOptionalElement(final WebElement webElement, final By byArgument) { + List foundElements = webElement.findElements(byArgument); + return foundElements.isEmpty() ? Optional.empty() : Optional.of(foundElements.get(0)); + } + + /** + * Returns the title text of the summary. + * + * @return the title text + */ + public String getTitleText() { + return summaryContent.getText(); + } + + /** + * Returns the number of failures of this junit run. + * + * @return the number of failures + */ + public int getNumberOfFailures() { + return failedTestLinks.size(); + } + + /** + * Returns the failures' names, in appearance order. + * + * @return the failures' names + */ + public List getFailureNames() { + return failedTestLinks.stream() + .map(WebElement::getText) + .collect(Collectors.toList()); + } + + /** + * Returns the failures' target links, accessible by its name. + * Method {@link #getFailureNames()} could help to retrieve the origin order. + * + * @return the failures' target links + */ + public Map getFailureTargetLinksByName() { + return failedTestLinks.stream() + .collect(Collectors.toMap(WebElement::getText, failedTestLink -> failedTestLink.getAttribute("href"))); + } + + /** + * Opens the build test results page. + * + * @return build test results page object. + */ + public BuildTestResults openBuildTestResults() { + return openPage(titleLink, BuildTestResults.class); + } + + /** + * Opens the detail view of a test. + * + * @param testName name of a test. + * @return test detail page object. + */ + public TestDetail openTestDetailView(final String testName) { + WebElement link = failedTestLinks.stream() + .filter(failedTestLink -> failedTestLink.getText().equals(testName)) + .findFirst() + .orElseThrow(() -> new NoSuchElementException(testName)); + + return openPage(link, TestDetail.class); + } + + private T openPage(final WebElement link, final Class type) { + String href = link.getAttribute("href"); + + link.click(); + return newInstance(type, injector, url(href)); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitJobConfiguration.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitJobConfiguration.java new file mode 100644 index 000000000..02ffc8ef7 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitJobConfiguration.java @@ -0,0 +1,82 @@ +package io.jenkins.plugins.analysis.junit; + +import org.jenkinsci.test.acceptance.po.AbstractStep; +import org.jenkinsci.test.acceptance.po.Control; +import org.jenkinsci.test.acceptance.po.Describable; +import org.jenkinsci.test.acceptance.po.Job; +import org.jenkinsci.test.acceptance.po.PageObject; +import org.jenkinsci.test.acceptance.po.PostBuildStep; + +/** + * {@link PageObject} representing the publish junit post build action in the freestyle job configuration. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@Describable("Publish JUnit test result report") +public class JUnitJobConfiguration extends AbstractStep implements PostBuildStep { + private final Control retainLogStandardOutputError = control("/keepLongStdio"); + private final Control allowEmptyResults = control("/allowEmptyResults"); + private final Control skipPublishingChecks = control("/skipPublishingChecks"); + private final Control skipMarkingBuildAsUnstableOnTestFailure = control("/skipMarkingBuildUnstable"); + private final Control healthScaleFactor = control("/healthScaleFactor"); + private final Control testResults = control("testResults"); + + /** + * Creates a new page object representing the junit summary on the build page of a job. + * + * @param parent a created job + * @param path path of the + */ + public JUnitJobConfiguration(Job parent, String path) { + super(parent, path); + } + + /** + * Set test results file. + * @param value to set test results + */ + public void setTestResults(String value) { + this.testResults.set(value); + } + + /** + * Set checkbox to retain standard log output error. + * @param shouldRetainLogStandardOutputError Check or uncheck checkbox + */ + public void setRetainLogStandardOutputError(boolean shouldRetainLogStandardOutputError) { + this.retainLogStandardOutputError.check(shouldRetainLogStandardOutputError); + } + + /** + * Set checkbox to allow empty results. + * @param shouldAllowEmptyResults Check or uncheck checkbox + */ + public void setAllowEmptyResults(boolean shouldAllowEmptyResults) { + this.allowEmptyResults.check(shouldAllowEmptyResults); + } + + /** + * Set checkbox to skip publishing checks. + * @param shouldSkipPublishingChecks Check or uncheck checkbox + */ + public void setSkipPublishingChecks(boolean shouldSkipPublishingChecks) { + this.retainLogStandardOutputError.check(shouldSkipPublishingChecks); + } + + /** + * Set checkbox to skip mark build as unstable on test failure. + * @param shouldSkipMarkingBuildAsUnstableOnTestFailure Check or uncheck checkbox + */ + public void setSkipMarkingBuildAsUnstableOnTestFailure(boolean shouldSkipMarkingBuildAsUnstableOnTestFailure) { + this.skipMarkingBuildAsUnstableOnTestFailure.check(shouldSkipMarkingBuildAsUnstableOnTestFailure); + } + + /** + * Set input value of health scale factor. + * @param value value to set the health scale factor + */ + public void setHealthScaleFactor(String value) { + this.healthScaleFactor.set(value); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitProjectSummary.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitProjectSummary.java new file mode 100644 index 000000000..db1d5cef4 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/JUnitProjectSummary.java @@ -0,0 +1,183 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.gargoylesoftware.htmlunit.ScriptResult; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +/** + * {@link PageObject} representing the JUnit summary on the job page. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class JUnitProjectSummary extends PageObject { + + private final WebElement summaryIcon; + private final WebElement summaryContent; + + private final WebElement titleLink; + + private final List buildChartEntries; + + /** + * Creates a new page object representing the JUnit summary on the job page. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public JUnitProjectSummary(final Build parent) throws JSONException { + super(parent, parent.url("")); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + WebElement junitJobSummaryTableEntry = getJunitJobSummaryTableEntry(mainPanel); + + summaryIcon = findIconInTableEntry(junitJobSummaryTableEntry).get(); + summaryContent = findContentInTableEntry(junitJobSummaryTableEntry).get(); + titleLink = summaryContent.findElement(By.cssSelector("a")); + buildChartEntries = initializeBuildChartEntries(); + } + + /** + * Gets the title text of the summary. + * + * @return the title text + */ + public String getTitleText() { + return summaryContent.getText(); + } + + /** + * Gets the number of failures of this JUnit run. + * + * @return the number of failures + */ + public int getNumberOfFailures() { + String summaryContentText = summaryContent.getText().trim(); + int fromIndex = summaryContentText.indexOf('(') + 1; + int toIndex = summaryContentText.indexOf(" ", fromIndex); + return Integer.parseInt(summaryContentText.substring(fromIndex, toIndex)); + } + + /** + * Gets the failure difference. + * + * @return the failure difference + */ + public int getFailureDifference() { + String summaryContentText = summaryContent.getText().trim(); + int fromIndex = summaryContentText.indexOf('/') + 2; + int toIndex = summaryContentText.length() - 1; + return Integer.parseInt(summaryContentText.substring(fromIndex, toIndex)); + } + + /** + * Gets the entries of the build chart. + * + * @return the entries of the build chart + */ + public List getBuildChartEntries() { + return buildChartEntries; + } + + private WebElement getJunitJobSummaryTableEntry(final WebElement mainPanel) { + List tables = mainPanel.findElements(By.cssSelector("table tbody tr")); + return tables.stream() + .filter(trElement -> findIconInTableEntry(trElement).isPresent()) + .filter(trElement -> findContentInTableEntry(trElement).isPresent()) + .findAny() + .orElseThrow(() -> new NoSuchElementException("junit job summary table")); + } + + private List initializeBuildChartEntries() throws JSONException { + String canvasJson = getJUnitChart(); + return canvasJsonToBuildChartEntries(canvasJson); + } + + private String getJUnitChart() { + Object result = executeScript(String.format( + "delete(window.Array.prototype.toJSON) %n" + + "return JSON.stringify(echarts.getInstanceByDom(document.getElementsByClassName(\"echarts-trend\")[0]).getOption())")); + ScriptResult scriptResult = new ScriptResult(result); + + return scriptResult.getJavaScriptResult().toString(); + } + + private Optional findIconInTableEntry(final WebElement tableEntry) { + return findOptionalElement(tableEntry, By.cssSelector("td img.icon-clipboard.icon-xlg")); + } + + private Optional findContentInTableEntry(final WebElement tableEntry) { + List foundElements = tableEntry.findElements(By.cssSelector("td")); + return foundElements.stream() + .filter(foundElement -> findOptionalElement(foundElement, By.cssSelector("a")).isPresent() && + findOptionalElement(foundElement, By.cssSelector("a")).get() + .getText() + .equals("Latest Test Result")) + .findFirst(); + } + + private Optional findOptionalElement(final WebElement webElement, final By byArgument) { + List foundElements = webElement.findElements(byArgument); + return foundElements.isEmpty() ? Optional.empty() : Optional.of(foundElements.get(0)); + } + + private List canvasJsonToBuildChartEntries(final String canvasJson) throws JSONException { + JSONObject jsonObject = new JSONObject(canvasJson); + JSONArray buildIds = jsonObject.getJSONArray("xAxis").getJSONObject(0).getJSONArray("data"); + JSONArray series = jsonObject.getJSONArray("series"); + + JSONArray failedTestNumbers = null; + JSONArray skippedTestNumbers = null; + JSONArray passedTestNumbers = null; + + int seriesLength = series.length(); + for (int i = 0; i < seriesLength; i++) { + JSONObject currentObject = series.getJSONObject(i); + String seriesName = currentObject.getString("name"); + switch (seriesName) { + case "Failed": + failedTestNumbers = currentObject.getJSONArray("data"); + break; + case "Skipped": + skippedTestNumbers = currentObject.getJSONArray("data"); + break; + case "Passed": + passedTestNumbers = currentObject.getJSONArray("data"); + break; + } + } + + JSONArray finalFailedTestNumbers = failedTestNumbers; + JSONArray finalSkippedTestNumbers = skippedTestNumbers; + JSONArray finalPassedTestNumbers = passedTestNumbers; + return IntStream.range(0, buildIds.length()) + .boxed() + .map(index -> { + try { + String buildIdString = buildIds.getString(index); + int buildId = Integer.parseInt(buildIdString.trim().substring(1)); + int failedTests = Integer.parseInt(finalFailedTestNumbers.getString(index)); + int skippedTests = Integer.parseInt(finalSkippedTestNumbers.getString(index)); + int passedTests = Integer.parseInt(finalPassedTestNumbers.getString(index)); + return new BuildChartEntry(buildId, skippedTests, failedTests, passedTests); + } + catch (JSONException e) { + throw new NoSuchElementException(); + } + }) + .collect(Collectors.toList()); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/README.md b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/README.md deleted file mode 100644 index 3091f8e3d..000000000 --- a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Please delete me after you added some files here. -The files to be added here are PageObjects for UI testing \ No newline at end of file diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/TestDetail.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/TestDetail.java new file mode 100644 index 000000000..f15d6d59d --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/TestDetail.java @@ -0,0 +1,159 @@ +package io.jenkins.plugins.analysis.junit; + +import java.net.URL; +import java.util.List; +import java.util.Optional; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +/** + * {@link PageObject} representing the detail view of a failed JUnit test. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class TestDetail extends PageObject { + private final WebElement title; + private final WebElement subTitle; + + private final Optional errorMessage; + private final Optional stackTrace; + private final Optional standardOutput; + + /** + * Creates a new page object representing the junit detail view of a failed JUnit test. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public TestDetail(final Build parent) { + super(parent, parent.url("testReport")); + + WebElement pageContent = getElement(By.cssSelector("#main-panel")); + + title = pageContent.findElement(By.cssSelector("h1")); + subTitle = pageContent.findElement(By.cssSelector("p")); + + int errorMessageHeaderIndex = -1; + int stackTraceHeaderIndex = -1; + int standardOutputHeaderIndex = -1; + List pageContentChildren = pageContent.findElements(By.cssSelector("*")); + + int counter = 0; + for (WebElement element : pageContentChildren) { + if (element.getTagName().equals("h3")) { + if (element.getText().equals("Error Message")) { + errorMessageHeaderIndex = counter; + } + else if (element.getText().equals("Stacktrace")) { + stackTraceHeaderIndex = counter; + } + else if (element.getText().equals("Standard Output")) { + standardOutputHeaderIndex = counter; + } + } + ++counter; + } + + errorMessage = errorMessageHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(errorMessageHeaderIndex + 1)) : Optional.empty(); + stackTrace = stackTraceHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(stackTraceHeaderIndex + 1)) : Optional.empty(); + standardOutput = standardOutputHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(standardOutputHeaderIndex + 1)) : Optional.empty(); + + } + + /** + * Creates an instance of the page displaying the details of the issues. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + @SuppressWarnings("unused") // Required to dynamically create page object using reflection + public TestDetail(final Injector injector, final URL url) { + super(injector, url); + + WebElement pageContent = getElement(By.cssSelector("#main-panel")); + + title = pageContent.findElement(By.cssSelector("h1")); + subTitle = pageContent.findElement(By.cssSelector("p")); + + int errorMessageHeaderIndex = -1; + int stackTraceHeaderIndex = -1; + int standardOutputHeaderIndex = -1; + List pageContentChildren = pageContent.findElements(By.cssSelector("*")); + + int counter = 0; + for (WebElement element : pageContentChildren) { + if (element.getTagName().equals("h3")) { + if (element.getText().equals("Error Message")) { + errorMessageHeaderIndex = counter; + } + else if (element.getText().equals("Stacktrace")) { + stackTraceHeaderIndex = counter; + } + else if (element.getText().equals("Standard Output")) { + standardOutputHeaderIndex = counter; + } + } + ++counter; + } + + errorMessage = errorMessageHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(errorMessageHeaderIndex + 1)) : Optional.empty(); + stackTrace = stackTraceHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(stackTraceHeaderIndex + 1)) : Optional.empty(); + standardOutput = standardOutputHeaderIndex >= 0 ? Optional.of(pageContentChildren.get(standardOutputHeaderIndex + 1)) : Optional.empty(); + } + + /** + * Returns the title of the detail view. + * + * @return the title of the detail view + */ + public String getTitle() { + return title.getText(); + } + + /** + * Returns the subtitle of the detail view, which is the test. + * + * @return the subtitle of the detail view + */ + public String getSubTitle() { + return subTitle.findElement(By.cssSelector("span")).getText() + subTitle.getText(); + } + + /** + * Returns the error message telling the user why the test has failed. + * + * @return the error message + */ + public Optional getErrorMessage() { + return errorMessage.map(WebElement::getText); + } + + /** + * Returns the stack trace providing more information about the failed test. + * + * @return the stack trace of the failed test + */ + public Optional getStackTrace() { + return stackTrace.map(WebElement::getText); + } + + /** + * Returns the standard output providing more information about the test. + * + * @return the standard output of the test + */ + public Optional getStandardOutput() { + return standardOutput.map(WebElement::getText); + } + +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResults.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResults.java new file mode 100644 index 000000000..d24b3b9fe --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResults.java @@ -0,0 +1,114 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.net.URL; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +import io.jenkins.plugins.analysis.junit.testresults.tableentry.PackageTableEntry; + +/** + * {@link PageObject} representing the first page of the test results of a build. + * + * @author Nikolas Paripovic + * @author Michael Müller + */ +public class BuildTestResults extends TestResultsWithFailedTestTable { + + private final List packageLinks; + private final List packageTableEntries; + + /** + * Creates a new page object representing the first page of the test results of a build. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public BuildTestResults(final Build parent) { + super(parent); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + packageLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + packageTableEntries = initializePackageTableEntries(mainPanel); + } + + /** + * Creates an instance of the page. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + public BuildTestResults(final Injector injector, final URL url) { + super(injector, url); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + packageLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + packageTableEntries = initializePackageTableEntries(mainPanel); + } + + /** + * Gets the entries of the package table. + * @return the package table entries + */ + public List getPackageTableEntries() { + return packageTableEntries; + } + + /** + * Open the test results page, filtered by package. + * @param packageName the package to filter + * @return the opened page + */ + public BuildTestResultsByPackage openTestResultsByPackage(final String packageName) { + WebElement link = packageLinks.stream() + .filter(packageLink -> packageLink.getText().equals(packageName)) + .findFirst() + .orElseThrow(() -> new NoSuchElementException(packageName)); + return openPage(link, BuildTestResultsByPackage.class); + } + + private List initializePackageTableEntries(final WebElement mainPanel) { + return TestResultsTableUtil.getTableItemsWithoutHeader(mainPanel).stream() + .map(this::webElementToPackageTableEntry) + .collect(Collectors.toList()); + } + + private PackageTableEntry webElementToPackageTableEntry(final WebElement trElement) { + List columns = trElement.findElements(By.cssSelector("td")); + + WebElement linkElement = columns.get(0).findElement(TestResultsTableUtil.aLink()); + String packageName = linkElement.getText(); + String packageLink = linkElement.getAttribute("href"); + String durationString = columns.get(1).getText().trim(); + int duration = Integer.parseInt(durationString.substring(0, durationString.length() - " ms".length())); + int fail = Integer.parseInt(columns.get(2).getText()); + String failDiffString = columns.get(3).getText(); + Optional failDiff = failDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(failDiffString)); + int skip = Integer.parseInt(columns.get(4).getText()); + String skipDiffString = columns.get(5).getText(); + Optional skipDiff = skipDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(skipDiffString)); + int pass = Integer.parseInt(columns.get(6).getText()); + String passDiffString = columns.get(7).getText(); + Optional passDiff = passDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(passDiffString)); + int total = Integer.parseInt(columns.get(8).getText()); + String totalDiffString = columns.get(9).getText(); + Optional totalDiff = totalDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(totalDiffString)); + + return new PackageTableEntry(packageName, packageLink, duration, fail, failDiff, skip, skipDiff, + pass, passDiff, total, totalDiff); + } + + +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByClass.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByClass.java new file mode 100644 index 000000000..ccea47da0 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByClass.java @@ -0,0 +1,101 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.net.URL; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +import io.jenkins.plugins.analysis.junit.TestDetail; +import io.jenkins.plugins.analysis.junit.testresults.tableentry.TestTableEntry; + +/** + * {@link PageObject} representing the third page of the test results of a build. + * This page contains tests filtered by a specific package and a specific class. + * + * @author Nikolas Paripovic + * @author Michael Müller + */ +public class BuildTestResultsByClass extends TestResults { + + private final List testLinks; + private final List testTableEntries; + + /** + * Creates a new page object representing the third page of the test results of a build. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public BuildTestResultsByClass(final Build parent) { + super(parent); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + testLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + testTableEntries = initializeTestTableEntries(mainPanel); + } + + /** + * Creates an instance of the page. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + public BuildTestResultsByClass(final Injector injector, final URL url) { + super(injector, url); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + testLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + testTableEntries = initializeTestTableEntries(mainPanel); + } + + /** + * Gets the entries of the test table. + * @return the test table entries + */ + public List getTestTableEntries() { + return testTableEntries; + } + + /** + * Open the test results page, filtered by test name. + * @param testName the test to filter + * @return the opened page + */ + public TestDetail openTestDetail(final String testName) { + WebElement link = testLinks.stream() + .filter(classLink -> classLink.getText().equals(testName)) + .findFirst() + .orElseThrow(() -> new NoSuchElementException(testName)); + return openPage(link, TestDetail.class); + } + + private List initializeTestTableEntries(final WebElement mainPanel) { + return TestResultsTableUtil.getTableItemsWithoutHeader(mainPanel).stream() + .map(this::webElementToTestTableEntry) + .collect(Collectors.toList()); + } + + private TestTableEntry webElementToTestTableEntry(final WebElement trElement) { + List columns = trElement.findElements(By.cssSelector("td")); + + WebElement linkElement = columns.get(0).findElement(TestResultsTableUtil.aLink()); + String testName = linkElement.getText(); + String testLink = linkElement.getAttribute("href"); + String durationString = columns.get(1).getText().trim(); + int duration = Integer.parseInt(durationString.substring(0, durationString.length() - " ms".length())); + String status = columns.get(2).getText(); + + return new TestTableEntry(testName, testLink, duration, status); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByPackage.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByPackage.java new file mode 100644 index 000000000..39661174b --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/BuildTestResultsByPackage.java @@ -0,0 +1,113 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.net.URL; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +import io.jenkins.plugins.analysis.junit.testresults.tableentry.ClassTableEntry; + +/** + * {@link PageObject} representing the second page of the test results of a build. + * This page contains tests filtered by a specific package. + * + * @author Nikolas Paripovic + * @author Michael Müller + */ +public class BuildTestResultsByPackage extends TestResultsWithFailedTestTable { + + private final List classLinks; + private final List classTableEntries; + + /** + * Creates a new page object representing the second page of the test results of a build. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public BuildTestResultsByPackage(final Build parent) { + super(parent); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + classLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + classTableEntries = initializeClassTableEntries(mainPanel); + } + + /** + * Creates an instance of the page. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + public BuildTestResultsByPackage(final Injector injector, final URL url) { + super(injector, url); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + classLinks = TestResultsTableUtil.getLinksOfTableItems(mainPanel); + classTableEntries = initializeClassTableEntries(mainPanel); + } + + /** + * Gets the entries of the class table. + * @return the class table entries + */ + public List getClassTableEntries() { + return classTableEntries; + } + + /** + * Open the test results page, filtered by class name. + * @param className the class to filter + * @return the opened page + */ + public BuildTestResultsByClass openTestResultsByClass(final String className) { + WebElement link = classLinks.stream() + .filter(classLink -> classLink.getText().equals(className)) + .findFirst() + .orElseThrow(() -> new NoSuchElementException(className)); + return openPage(link, BuildTestResultsByClass.class); + } + + private List initializeClassTableEntries(final WebElement mainPanel) { + return TestResultsTableUtil.getTableItemsWithoutHeader(mainPanel).stream() + .map(this::webElementToClassTableEntry) + .collect(Collectors.toList()); + } + + private ClassTableEntry webElementToClassTableEntry(final WebElement trElement) { + List columns = trElement.findElements(By.cssSelector("td")); + + WebElement linkElement = columns.get(0).findElement(TestResultsTableUtil.aLink()); + String className = linkElement.getText(); + String classLink = linkElement.getAttribute("href"); + String durationString = columns.get(1).getText().trim(); + int duration = Integer.parseInt(durationString.substring(0, durationString.length() - " ms".length())); + int fail = Integer.parseInt(columns.get(2).getText()); + String failDiffString = columns.get(3).getText(); + Optional failDiff = failDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(failDiffString)); + int skip = Integer.parseInt(columns.get(4).getText()); + String skipDiffString = columns.get(5).getText(); + Optional skipDiff = skipDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(skipDiffString)); + int pass = Integer.parseInt(columns.get(6).getText()); + String passDiffString = columns.get(7).getText(); + Optional passDiff = passDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(passDiffString)); + int total = Integer.parseInt(columns.get(8).getText()); + String totalDiffString = columns.get(9).getText(); + Optional totalDiff = totalDiffString.isEmpty() ? Optional.empty() : Optional.of(Integer.parseInt(totalDiffString)); + + return new ClassTableEntry(className, classLink, duration, fail, failDiff, skip, skipDiff, + pass, passDiff, total, totalDiff); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResults.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResults.java new file mode 100644 index 000000000..abb6cabf1 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResults.java @@ -0,0 +1,91 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.net.URL; +import java.util.List; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +/** + * Abstract {@link PageObject} base class for test results pages. + * + * @author Nikolas Paripovic + * @author Michael Müller + */ +public abstract class TestResults extends PageObject { + + private final WebElement numberOfFailuresElement; + private final WebElement numberOfTestsElement; + + /** + * Creates a new abstract page object as a base class for test results pages. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public TestResults(final Build parent) { + super(parent, parent.url("testReport")); + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + numberOfFailuresElement = initializeNumberOfFailuresElement(mainPanel); + numberOfTestsElement = initializeNumberOfTestsElement(mainPanel); + } + + /** + * Creates an instance of the page. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + public TestResults(final Injector injector, final URL url) { + super(injector, url); + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + numberOfFailuresElement = initializeNumberOfFailuresElement(mainPanel); + numberOfTestsElement = initializeNumberOfTestsElement(mainPanel); + } + + /** + * Gets the number of failed tests in this build. + * @return the number of failures + */ + public int getNumberOfFailures() { + String text = numberOfFailuresElement.getText().trim(); + return Integer.parseInt(text.substring(0, text.indexOf(' '))); + } + + /** + * Gets the number of processed tests in this build. + * @return the number of tests + */ + public int getNumberOfTests() { + String text = numberOfTestsElement.getText().trim(); + return Integer.parseInt(text.substring(0, text.indexOf(' '))); + } + + protected T openPage(final WebElement link, final Class type) { + String href = link.getAttribute("href"); + link.click(); + return newInstance(type, injector, url(href)); + } + + private WebElement initializeNumberOfTestsElement(final WebElement mainPanel) { + List testsNumberElements = getTestsNumberElements(mainPanel); + return testsNumberElements.get(testsNumberElements.size() - 1); + } + + private WebElement initializeNumberOfFailuresElement(final WebElement mainPanel) { + List testsNumberElements = getTestsNumberElements(mainPanel); + return testsNumberElements.get(0); + } + + private List getTestsNumberElements(final WebElement mainPanel) { + return mainPanel.findElements(By.cssSelector("h1 + div div")); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsTableUtil.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsTableUtil.java new file mode 100644 index 000000000..9c7094b1a --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsTableUtil.java @@ -0,0 +1,43 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.util.List; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +/** + * Util class for repeating parsing tasks. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class TestResultsTableUtil { + static By aLink() { + return By.xpath("a[not(@id)]"); + } + + /** + * Gets the test result table items, omitting the header table item. + * @param mainPanelElement the main panel element + * @return the test result table items + */ + public static List getTableItemsWithoutHeader(final WebElement mainPanelElement) { + WebElement testResultTable = mainPanelElement.findElement(By.cssSelector("#testresult")); + + List testResultTableBodies = testResultTable.findElements(By.cssSelector("tbody")); + WebElement testResultTableBodyWithoutHeader = testResultTableBodies.get(0); + return testResultTableBodyWithoutHeader.findElements(By.cssSelector("tr")); + } + + /** + * Gets the links of the test result table items. + * @param mainPanelElement the main panel element + * @return the links of the test result table items + */ + public static List getLinksOfTableItems(final WebElement mainPanelElement) { + return getTableItemsWithoutHeader(mainPanelElement).stream() + .map(trElement -> trElement.findElements(By.cssSelector("td")).get(0).findElement(aLink())) + .collect(Collectors.toList()); + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsWithFailedTestTable.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsWithFailedTestTable.java new file mode 100644 index 000000000..06e89c714 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/TestResultsWithFailedTestTable.java @@ -0,0 +1,204 @@ +package io.jenkins.plugins.analysis.junit.testresults; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.google.inject.Injector; + +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.PageObject; + +import io.jenkins.plugins.analysis.junit.TestDetail; +import io.jenkins.plugins.analysis.junit.testresults.tableentry.FailedTestTableEntry; + +/** + * Abstract {@link PageObject} base class for test results pages including a failed test table. + * + * @author Nikolas Paripovic + * @author Michael Müller + */ +public abstract class TestResultsWithFailedTestTable extends TestResults { + + private final Optional failedTestsTable; + + private final List failedTestLinks; + private final List failedTestTableEntries; + + /** + * Creates a new abstract page object as a base class for test results pages. + * + * @param parent + * a finished build configured with a static analysis tool + */ + public TestResultsWithFailedTestTable(final Build parent) { + super(parent); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + failedTestsTable = initialFailedTestsTable(mainPanel); + + if (failedTestsTable.isPresent()) { + failedTestLinks = initializeFailedTestLinks(failedTestsTable.get()); + failedTestTableEntries = initializeFailedTestTableEntries(failedTestsTable.get()); + } + else { + failedTestLinks = Collections.emptyList(); + failedTestTableEntries = Collections.emptyList(); + } + } + + /** + * Creates an instance of the page. This constructor is used for injecting a + * filtered instance of the page (e.g. by clicking on links which open a filtered instance of a AnalysisResult. + * + * @param injector + * the injector of the page + * @param url + * the url of the page + */ + public TestResultsWithFailedTestTable(final Injector injector, final URL url) { + super(injector, url); + + WebElement mainPanel = getElement(By.cssSelector("#main-panel")); + failedTestsTable = initialFailedTestsTable(mainPanel); + + if (failedTestsTable.isPresent()) { + failedTestLinks = initializeFailedTestLinks(failedTestsTable.get()); + failedTestTableEntries = initializeFailedTestTableEntries(failedTestsTable.get()); + } + else { + failedTestLinks = Collections.emptyList(); + failedTestTableEntries = Collections.emptyList(); + } + + } + + /** + * Checks whether the failed test table exists or not. + * With this knowledge, you can call {@link #getFailedTestTableEntries()} + * @return whether the failed test table exists or not + */ + public boolean failedTestTableExists() { + return failedTestsTable.isPresent(); + } + + /** + * Gets the table entries of the failed tests. + * @return the failed test table entries. + */ + public List getFailedTestTableEntries() { + return failedTestTableEntries; + } + + /** + * Opens the test detail page. + * @param testName the test to open + * @return the opened page + */ + public TestDetail openTestDetail(final String testName) { + WebElement link = failedTestLinks.stream() + .filter(failedTestLink -> failedTestLink.getText().equals(testName)) + .findFirst() + .orElseThrow(() -> new NoSuchElementException(testName)); + return openPage(link, TestDetail.class); + } + + private List getFailedTestsTableItemsWithoutHeader(final WebElement failedTestsTableElement) { + return failedTestsTableElement.findElements(By.cssSelector("tbody tr")); + } + + private List initializeFailedTestLinks(final WebElement failedTestsTableElement) { + List failedTestsTableItemsWithoutHeader = getFailedTestsTableItemsWithoutHeader(failedTestsTableElement); + return failedTestsTableItemsWithoutHeader.stream() + .map(trElement -> trElement.findElements(By.cssSelector("td")) + .get(0) + .findElement(TestResultsTableUtil.aLink())) + .collect(Collectors.toList()); + } + + private List initializeFailedTestTableEntries(final WebElement failedTestsTableElement) { + List failedTestsTableItemsWithoutHeader = getFailedTestsTableItemsWithoutHeader(failedTestsTableElement); + return failedTestsTableItemsWithoutHeader.stream() + .map(this::webElementToFailedTestTableEntry) + .collect(Collectors.toList()); + } + + private Optional initialFailedTestsTable(final WebElement mainPanel) { + int failedTestsTableIndex = -1; + List pageContentChildren = mainPanel.findElements(By.cssSelector("*")); + + int counter = 0; + for (WebElement element : pageContentChildren) { + if (element.getTagName().equals("h2")) { + if (element.getText().equals("All Failed Tests")) { + failedTestsTableIndex = counter; + } + } + ++counter; + } + return failedTestsTableIndex >= 0 ? Optional.of(pageContentChildren.get(failedTestsTableIndex + 1)) : Optional.empty(); + } + + private FailedTestTableEntry webElementToFailedTestTableEntry(final WebElement trElement) { + List columns = trElement.findElements(By.cssSelector("td")); + + WebElement linkElement = columns.get(0).findElement(TestResultsTableUtil.aLink()); + String testName = linkElement.getText(); + String testLink = linkElement.getAttribute("href"); + String durationString = columns.get(1).getText().trim(); + int duration = Integer.parseInt(durationString.substring(0, durationString.length() - " ms".length())); + int age = Integer.parseInt(columns.get(2).findElement(By.cssSelector("a")).getText()); + + WebElement expandLink = columns.get(0).findElement(By.cssSelector("a[title=\"Show details\"]")); + expandLink.click(); + + WebElement failureSummary = columns.get(0).findElement(By.cssSelector("div.failure-summary")); + + List showErrorDetailsLinks = failureSummary.findElements(By.cssSelector("a[title=\"Show Error Details\"]")); + if (showErrorDetailsLinks.size() > 0) { + WebElement showErrorDetailsLink = showErrorDetailsLinks.get(0); + if (!showErrorDetailsLink.getAttribute("style").contains("display: none;")) { + showErrorDetailsLink.click(); + } + } + + List showStackTraceLinks = failureSummary.findElements(By.cssSelector("a[title=\"Show Stack Trace\"]")); + if (showStackTraceLinks.size() > 0) { + WebElement showStackTraceLink = showStackTraceLinks.get(0); + if (!showStackTraceLink.getAttribute("style").contains("display: none;")) { + showStackTraceLink.click(); + } + } + + List failureSummaryChildren = failureSummary.findElements(By.cssSelector("*")); + int counter = 0; + int errorDetailsHeaderIndex = -1; + int stackTraceHeaderIndex = -1; + for (WebElement element : failureSummaryChildren) { + if (element.getTagName().equals("h4")) { + if (element.findElements(By.cssSelector("a[title=\"Show Error Details\"]")).size() > 0) { + errorDetailsHeaderIndex = counter; + } + else if (element.findElements(By.cssSelector("a[title=\"Show Stack Trace\"]")).size() > 0) { + stackTraceHeaderIndex = counter; + } + } + ++counter; + } + + Optional errorDetailsPreElement = errorDetailsHeaderIndex == -1 ? Optional.empty() : Optional.of(failureSummaryChildren.get(errorDetailsHeaderIndex + 1)); + Optional stackTracePreElement = stackTraceHeaderIndex == -1 ? Optional.empty() : Optional.of(failureSummaryChildren.get(stackTraceHeaderIndex + 1)); + + Optional errorDetails = errorDetailsPreElement.map(WebElement::getText); + Optional stackTrace = stackTracePreElement.map(WebElement::getText); + + return new FailedTestTableEntry(testName, testLink, duration, age, errorDetails, stackTrace); + } + +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/ClassTableEntry.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/ClassTableEntry.java new file mode 100644 index 000000000..3656f32f3 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/ClassTableEntry.java @@ -0,0 +1,152 @@ +package io.jenkins.plugins.analysis.junit.testresults.tableentry; + +import java.util.Optional; + +/** + * The entry of a class table listing test results of current and previous builds. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class ClassTableEntry { + + private final String className; + + private final String classLink; + + private final int duration; + + private final int fail; + + private final Optional failDiff; + + private final int skip; + + private final Optional skipDiff; + + private final int pass; + + private final Optional passDiff; + + private final int total; + + private final Optional totalDiff; + + /** + * Custom constructor. Creates object. + * @param className the class name property + * @param classLink the target location class link + * @param duration the duration property + * @param fail the fail property + * @param failDiff the fail diff property + * @param skip the skip property + * @param skipDiff the skip diff property + * @param pass the pass property + * @param passDiff the pass diff property + * @param total the total property + * @param totalDiff the total diff property + */ + public ClassTableEntry(final String className, final String classLink, final int duration, final int fail, + final Optional failDiff, final int skip, + final Optional skipDiff, final int pass, final Optional passDiff, final int total, final Optional totalDiff) { + this.className = className; + this.classLink = classLink; + this.duration = duration; + this.fail = fail; + this.failDiff = failDiff; + this.skip = skip; + this.skipDiff = skipDiff; + this.pass = pass; + this.passDiff = passDiff; + this.total = total; + this.totalDiff = totalDiff; + } + + /** + * Gets the property class name. + * @return the class name property + */ + public String getClassName() { + return className; + } + + /** + * Gets the target location when clicking on the class name. + * @return the class link + */ + public String getClassLink() { + return classLink; + } + + /** + * Gets the property duration. + * @return the duration property + */ + public int getDuration() { + return duration; + } + + /** + * Gets the property fail. + * @return the fail property + */ + public int getFail() { + return fail; + } + + /** + * Gets the optional property fail diff. + * @return the fail diff property + */ + public Optional getFailDiff() { + return failDiff; + } + + /** + * Gets the property skip. + * @return the skip property + */ + public int getSkip() { + return skip; + } + + /** + * Gets the optional property skip diff. + * @return the skip diff property + */ + public Optional getSkipDiff() { + return skipDiff; + } + + /** + * Gets the property pass. + * @return the pass property + */ + public int getPass() { + return pass; + } + + /** + * Gets the optional property pass diff. + * @return the pass diff property + */ + public Optional getPassDiff() { + return passDiff; + } + + /** + * Gets the property total. + * @return the total property + */ + public int getTotal() { + return total; + } + + /** + * Gets the optional property total diff. + * @return the total diff property + */ + public Optional getTotalDiff() { + return totalDiff; + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/FailedTestTableEntry.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/FailedTestTableEntry.java new file mode 100644 index 000000000..10ec274a9 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/FailedTestTableEntry.java @@ -0,0 +1,92 @@ +package io.jenkins.plugins.analysis.junit.testresults.tableentry; + +import java.util.Optional; + +/** + * The entry of a failed test table listing failed tests of the current build. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class FailedTestTableEntry { + + private final String testName; + + private final String testLink; + + private final int duration; + + private final int age; + + private final Optional errorDetails; + + private final Optional stackTrace; + + /** + * Costum constructor. Creates object. + * @param testName the test name property + * @param testLink the target location test link + * @param duration the duration property + * @param age the age property + * @param errorDetails the error details property + * @param stackTrace the stack trace property + */ + public FailedTestTableEntry(final String testName, final String testLink, final int duration, final int age, + final Optional errorDetails, + final Optional stackTrace) { + this.testName = testName; + this.testLink = testLink; + this.duration = duration; + this.age = age; + this.errorDetails = errorDetails; + this.stackTrace = stackTrace; + } + + /** + * Gets the test name property. + * @return the test name property + */ + public String getTestName() { + return testName; + } + + /** + * Gets the target location when clicking on the test name. + * @return the test link + */ + public String getTestLink() { + return testLink; + } + + /** + * Gets the duration property. + * @return the duration property + */ + public int getDuration() { + return duration; + } + + /** + * Gets the age property. + * @return the age property + */ + public int getAge() { + return age; + } + + /** + * Gets the optional error details. + * @return the error details + */ + public Optional getErrorDetails() { + return errorDetails; + } + + /** + * Gets the optional stack trace. + * @return the stack trace + */ + public Optional getStackTrace() { + return stackTrace; + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/PackageTableEntry.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/PackageTableEntry.java new file mode 100644 index 000000000..0db3fe75c --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/PackageTableEntry.java @@ -0,0 +1,151 @@ +package io.jenkins.plugins.analysis.junit.testresults.tableentry; + +import java.util.Optional; + +/** + * The entry of a package table listing test results of current and previous builds. + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class PackageTableEntry { + + private final String packageName; + + private final String packageLink; + + private final int duration; + + private final int fail; + + private final Optional failDiff; + + private final int skip; + + private final Optional skipDiff; + + private final int pass; + + private final Optional passDiff; + + private final int total; + + private final Optional totalDiff; + + /** + * Custom constructor. Creates object. + * @param packageName the package name property + * @param packageLink the target location package link + * @param duration the duration property + * @param fail the fail property + * @param failDiff the fail diff property + * @param skip the skip property + * @param skipDiff the skip diff property + * @param pass the pass property + * @param passDiff the pass diff property + * @param total the total property + * @param totalDiff the total diff property + */ + public PackageTableEntry(final String packageName, final String packageLink, final int duration, final int fail, + final Optional failDiff, final int skip, + final Optional skipDiff, final int pass, final Optional passDiff, final int total, final Optional totalDiff) { + this.packageName = packageName; + this.packageLink = packageLink; + this.duration = duration; + this.fail = fail; + this.failDiff = failDiff; + this.skip = skip; + this.skipDiff = skipDiff; + this.pass = pass; + this.passDiff = passDiff; + this.total = total; + this.totalDiff = totalDiff; + } + + /** + * Gets the package name property. + * @return the package name property + */ + public String getPackageName() { + return packageName; + } + + /** + * Gets the target location when clicking on the package name. + * @return the package link + */ + public String getPackageLink() { + return packageLink; + } + + /** + * Gets the duration property. + * @return the duration property + */ + public int getDuration() { + return duration; + } + + /** + * Gets the fail property. + * @return the fail property + */ + public int getFail() { + return fail; + } + + /** + * Gets the optional fail diff property. + * @return the fail diff property + */ + public Optional getFailDiff() { + return failDiff; + } + + /** + * Gets the skip property. + * @return the skip property + */ + public int getSkip() { + return skip; + } + + /** + * Gets the optional skip diff property. + * @return the skip diff property + */ + public Optional getSkipDiff() { + return skipDiff; + } + + /** + * Gets the pass property. + * @return the pass property + */ + public int getPass() { + return pass; + } + + /** + * Gets the optional pass diff property. + * @return the pass diff property + */ + public Optional getPassDiff() { + return passDiff; + } + + /** + * Gets the total property. + * @return the total property + */ + public int getTotal() { + return total; + } + + /** + * Gets the optional total diff property. + * @return the total diff property + */ + public Optional getTotalDiff() { + return totalDiff; + } +} diff --git a/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/TestTableEntry.java b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/TestTableEntry.java new file mode 100644 index 000000000..ebf020207 --- /dev/null +++ b/ui-tests/src/main/java/io/jenkins/plugins/analysis/junit/testresults/tableentry/TestTableEntry.java @@ -0,0 +1,63 @@ +package io.jenkins.plugins.analysis.junit.testresults.tableentry; + +/** + * The entry of a test table listing test results of the current build. + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class TestTableEntry { + + private final String testName; + + private final String testLink; + + private final int duration; + + private final String status; + + /** + * Custom constructor. Creates object. + * @param testName the test nam property + * @param testLink the target location test link + * @param duration the duration property + * @param status the status property + */ + public TestTableEntry(final String testName, final String testLink, final int duration, final String status) { + this.testName = testName; + this.testLink = testLink; + this.duration = duration; + this.status = status; + } + + /** + * Gets the test name property. + * @return the test name property + */ + public String getTestName() { + return testName; + } + + /** + * Gets the target location when clicking on the test name. + * @return the test link + */ + public String getTestLink() { + return testLink; + } + + /** + * Gets the duration property. + * @return the duration property + */ + public int getDuration() { + return duration; + } + + /** + * Gets the status property. + * @return the status property + */ + public String getStatus() { + return status; + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildSummaryTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildSummaryTest.java new file mode 100644 index 000000000..e9aa85d63 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildSummaryTest.java @@ -0,0 +1,54 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; + +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the JUnit tests summary on the build summary page of a job. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@WithPlugins("junit") +public class BuildSummaryTest extends AbstractJUnitTest { + + /** + * Verifies shown failure count when no failures occurred. + */ + @Test + public void verifySummaryNoFailures() { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/success/com.simple.project.AppTest.txt", "/success/TEST-com.simple.project.AppTest.xml"), "SUCCESS"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + + assertThat(buildSummary.getTitleText()).containsAnyOf("no failures", "0 failures"); + assertThat(buildSummary.getFailureNames()).isEmpty(); + } + + /** + * Verifies shown failure count and listed failures when failures occurred. + */ + @Test + public void verifySummaryWithFailures() { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/parameterized/junit.xml", "/parameterized/testng.xml"), "UNSTABLE"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + + assertThat(buildSummary.getTitleText()).contains("6 failures"); + assertThat(buildSummary.getFailureNames()) + .containsExactlyInAnyOrder("JUnit.testScore[0]", "JUnit.testScore[1]", "JUnit.testScore[2]", "TestNG.testScore", "TestNG.testScore", "TestNG.testScore"); + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByClassTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByClassTest.java new file mode 100644 index 000000000..d0c0de6df --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByClassTest.java @@ -0,0 +1,105 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.testresults.BuildTestResultsByClass; +import io.jenkins.plugins.analysis.junit.testresults.BuildTestResultsByPackage; +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * Tests the published unit tests results of a build which are filtered by a class. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@WithPlugins("junit") +public class BuildTestResultsByClassTest extends AbstractJUnitTest { + + /** + * Verifies failed tests are listed with correct test status. + */ + @Test + public void verifyWithFailures() { + + BuildTestResultsByClass buildTestResultsByClass = createBuildJobAndOpenBuildTestResultsByClass( + "/failure/three_failed_two_succeeded.xml", + "UNSTABLE", + "com.simple.project", + "AppTest" + ); + + assertThat(buildTestResultsByClass.getTestTableEntries()).extracting(List::size).isEqualTo(2); + + TestUtils.assertElementInCollection(buildTestResultsByClass.getTestTableEntries(), + testTableEntry -> testTableEntry.getTestName().equals("testAppFailNoMessage"), + testTableEntry -> testTableEntry.getTestName().equals("testAppFailNoStacktrace")); + + TestUtils.assertElementInCollection(buildTestResultsByClass.getTestTableEntries(), + testTableEntry -> testTableEntry.getStatus().equals("Failed"), + testTableEntry -> testTableEntry.getStatus().equals("Failed")); + } + + /** + * Verifies passed tests are listed with correct test status. + */ + @Test + public void verifyWithNoFailures() { + + BuildTestResultsByClass buildTestResultsByClass = createBuildJobAndOpenBuildTestResultsByClass( + "/success/TEST-com.simple.project.AppTest.xml", + "SUCCESS", + "com.simple.project", + "AppTest" + ); + + assertThat(buildTestResultsByClass.getTestTableEntries()).extracting(List::size).isEqualTo(1); + + TestUtils.assertElementInCollection(buildTestResultsByClass.getTestTableEntries(), + testTableEntry -> testTableEntry.getTestName().equals("testApp")); + + TestUtils.assertElementInCollection(buildTestResultsByClass.getTestTableEntries(), + testTableEntry -> testTableEntry.getStatus().equals("Passed")); + } + + /** + * Verifies test has status "Regression" when test failed after success in previous build. + */ + @Test + public void verifiesTestHasStatusRegressionWhenTestFailedAfterSuccessfulTestBefore() { + + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(lastBuild); + BuildTestResultsByClass buildTestResultsByClass = buildSummary + .openBuildTestResults() + .openTestResultsByPackage("com.another.simple.project") + .openTestResultsByClass("ApplicationTest"); + + TestUtils.assertElementInCollection(buildTestResultsByClass.getTestTableEntries(), + tableEntry -> tableEntry.getTestName().equals("testAppFail") && tableEntry.getStatus().equals("Failed"), + tableEntry -> tableEntry.getTestName().equals("testApplicationSuccess") && tableEntry.getStatus().equals("Regression")); + } + + private BuildTestResultsByClass createBuildJobAndOpenBuildTestResultsByClass(String testResultsReport, + String expectedBuildResult, String packageName, String className) { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList(testResultsReport), expectedBuildResult); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + BuildTestResultsByPackage buildTestResultsByPackage = buildSummary + .openBuildTestResults() + .openTestResultsByPackage(packageName); + + return buildTestResultsByPackage.openTestResultsByClass(className); + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByPackageTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByPackageTest.java new file mode 100644 index 000000000..a7396a5f8 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsByPackageTest.java @@ -0,0 +1,114 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.testresults.BuildTestResultsByPackage; +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static io.jenkins.plugins.analysis.junit.testresults.BuildTestResultsByPackageAssert.*; + +/** + * Tests the published unit test results of a build which are filtered by a package. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@WithPlugins("junit") +public class BuildTestResultsByPackageTest extends AbstractJUnitTest { + + /** + * Verifies correct numbers of failed and passed tests, failed tests table and the test classes table are shown correctly. + */ + @Test + public void verifyWithFailures() { + + BuildTestResultsByPackage buildTestResultsByPackage = createBuildJobAndOpenBuildTestResultsByPackage( + "/failure/three_failed_two_succeeded.xml", + "UNSTABLE", + "com.simple.project" + ); + + assertThat(buildTestResultsByPackage) + .hasNumberOfFailures(2) + .hasNumberOfTests(3); + + assertThat(buildTestResultsByPackage.failedTestTableExists()).isTrue(); + assertThat(buildTestResultsByPackage.getFailedTestTableEntries()).extracting(List::size).isEqualTo(2); + + TestUtils.assertElementInCollection(buildTestResultsByPackage.getFailedTestTableEntries(), + failedTestTableEntry -> failedTestTableEntry.getTestName() + .equals("com.simple.project.AppTest.testAppFailNoMessage"), + failedTestTableEntry -> failedTestTableEntry.getTestName() + .equals("com.simple.project.AppTest.testAppFailNoStacktrace")); + + assertThat(buildTestResultsByPackage.getClassTableEntries()).extracting(List::size).isEqualTo(2); + + TestUtils.assertElementInCollection(buildTestResultsByPackage.getClassTableEntries(), + classTableEntry -> classTableEntry.getClassName().equals("AppTest"), + classTableEntry -> classTableEntry.getClassName().equals("ApplicationTest")); + } + + /** + * Verifies correct numbers of failed and passed tests, no failed tests table is shown and the test classes table is shown correctly. + */ + @Test + public void verifyWithNoFailures() { + + BuildTestResultsByPackage buildTestResultsByPackage = createBuildJobAndOpenBuildTestResultsByPackage( + "/success/TEST-com.simple.project.AppTest.xml", + "SUCCESS", + "com.simple.project" + ); + + assertThat(buildTestResultsByPackage) + .hasNumberOfFailures(0) + .hasNumberOfTests(1); + + assertThat(buildTestResultsByPackage.failedTestTableExists()).isFalse(); + + assertThat(buildTestResultsByPackage.getClassTableEntries()).extracting(List::size).isEqualTo(1); + + TestUtils.assertElementInCollection(buildTestResultsByPackage.getClassTableEntries(), + classTableEntry -> classTableEntry.getClassName().equals("AppTest")); + } + + /** + * Verifies increase/ decrease in failure/ passed tests count of two consecutive builds are shown correctly. + */ + @Test + public void verifiesTestHasStatusRegressionWhenTestFailedAfterSuccessfulTestBefore() { + + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(lastBuild); + BuildTestResultsByPackage buildTestResultsByPackage = buildSummary + .openBuildTestResults() + .openTestResultsByPackage("com.another.simple.project"); + + TestUtils.assertElementInCollection(buildTestResultsByPackage.getClassTableEntries(), + packageTableEntry -> packageTableEntry.getFailDiff().get().equals(1) + && packageTableEntry.getPassDiff().get().equals(-1) + ); + } + + private BuildTestResultsByPackage createBuildJobAndOpenBuildTestResultsByPackage(String testResultsReport, + String expectedBuildResult, String packageName) { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList(testResultsReport), expectedBuildResult); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + return buildSummary + .openBuildTestResults() + .openTestResultsByPackage(packageName); + + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsTest.java new file mode 100644 index 000000000..38e1a3af8 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/BuildTestResultsTest.java @@ -0,0 +1,105 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.testresults.BuildTestResults; +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static io.jenkins.plugins.analysis.junit.testresults.BuildTestResultsAssert.*; +import static org.assertj.core.api.AssertionsForClassTypes.*; + +/** + * Tests the detail view of failed unit tests of a build. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@WithPlugins("junit") +public class BuildTestResultsTest extends AbstractJUnitTest { + + /** + * Verifies correct numbers total tests and failed tests shown when failures occurred. + * In addition listed names of failed tests and corresponding packages are verified. + */ + @Test + public void verifyWithFailures() { + + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/failure/three_failed_two_succeeded.xml"), "UNSTABLE"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + BuildTestResults buildTestResults = buildSummary.openBuildTestResults(); + + assertThat(buildTestResults) + .hasNumberOfFailures(3) + .hasNumberOfTests(5); + + assertThat(buildTestResults.failedTestTableExists()).isTrue(); + assertThat(buildTestResults.getFailedTestTableEntries()).extracting(List::size).isEqualTo(3); + + TestUtils.assertElementInCollection(buildTestResults.getFailedTestTableEntries(), + failedTestTableEntry -> failedTestTableEntry.getTestName().equals("com.simple.project.AppTest.testAppFailNoMessage"), + failedTestTableEntry -> failedTestTableEntry.getTestName().equals("com.simple.project.AppTest.testAppFailNoStacktrace"), + failedTestTableEntry -> failedTestTableEntry.getTestName().equals("com.another.simple.project.ApplicationTest.testAppFail")); + + assertThat(buildTestResults.getPackageTableEntries()).extracting(List::size).isEqualTo(2); + + TestUtils.assertElementInCollection(buildTestResults.getPackageTableEntries(), + packageTableEntry -> packageTableEntry.getPackageName().equals("com.simple.project"), + packageTableEntry -> packageTableEntry.getPackageName().equals("com.another.simple.project")); + } + + /** + * Verifies correct numbers and lists of total tests and failed tests when no failures occurred. + */ + @Test + public void verifyNoFailures() { + + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/success/TEST-com.simple.project.AppTest.xml"), "SUCCESS"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + BuildTestResults buildTestResults = buildSummary.openBuildTestResults(); // TODO: Better access by navigation icon? + + assertThat(buildTestResults) + .hasNumberOfFailures(0) + .hasNumberOfTests(1); + + assertThat(buildTestResults.failedTestTableExists()).isFalse(); + assertThat(buildTestResults.getFailedTestTableEntries()).extracting(List::size).isEqualTo(0); + + assertThat(buildTestResults.getPackageTableEntries()).extracting(List::size).isEqualTo(1); + + TestUtils.assertElementInCollection(buildTestResults.getPackageTableEntries(), + packageTableEntry -> packageTableEntry.getPackageName().equals("com.simple.project")); + } + + /** + * Verifies increase/ decrease in failure/ passed tests count of two consecutive builds are shown correctly. + */ + @Test + public void verifyFailureAndPassedTestsDifferenceToPreviousBuild() { + + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + JUnitBuildSummary buildSummary = new JUnitBuildSummary(lastBuild); + BuildTestResults buildTestResults = buildSummary.openBuildTestResults(); + + assertThat(buildTestResults.getPackageTableEntries()).extracting(List::size).isEqualTo(2); + + TestUtils.assertElementInCollection(buildTestResults.getPackageTableEntries(), + packageTableEntry -> packageTableEntry.getFailDiff().get().equals(1) + && packageTableEntry.getPassDiff().get().equals(-1), + packageTableEntry -> !packageTableEntry.getFailDiff().isPresent() + && !packageTableEntry.getPassDiff().isPresent() + ); + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/JobConfigurationTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/JobConfigurationTest.java new file mode 100644 index 000000000..dfdbf1763 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/JobConfigurationTest.java @@ -0,0 +1,72 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; +import java.util.Collections; +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.FreeStyleJob; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +/** + * Tests the job configuration of the JUnit test results report publisher. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class JobConfigurationTest extends AbstractJUnitTest { + /** + * Tests if build is successful with test failures when checkbox "Skip publishing checks" is checked. + */ + @Test + public void successfulBuildWhenSkipMarkingBuildAsUnstableOnTestFailureChecked() { + FreeStyleJob j = jenkins.jobs.create(); + j.configure(); + j.copyResource(resource("/failure/com.simple.project.AppTest.txt")); + j.copyResource(resource("/failure/TEST-com.simple.project.AppTest.xml")); + JUnitJobConfiguration publisher = j.addPublisher(JUnitJobConfiguration.class); + publisher.setTestResults("*.xml"); + publisher.setSkipMarkingBuildAsUnstableOnTestFailure(true); + j.save(); + + Build build = j.startBuild(); + assertThat(build.getResult()).isEqualTo("SUCCESS"); + } + + /** + * Tests if build is successful with no test results when checkbox "Allow empty results" is checked. + */ + @Test + public void successfulBuildWhenEmptyTestResultsChecked() { + FreeStyleJob j = jenkins.jobs.create(); + j.configure(); + JUnitJobConfiguration publisher = j.addPublisher(JUnitJobConfiguration.class); + publisher.setAllowEmptyResults(true); + j.save(); + + j.startBuild().shouldSucceed(); + } + + /** + * Tests if long standard output is not truncated in test details when checkbox "Retain long standard output/error" is checked. + */ + @Test + public void retainLongStandardOutputError() { + FreeStyleJob j = jenkins.jobs.create(); + j.configure(); + j.copyResource(resource("/success/junit-with-long-output.xml")); + JUnitJobConfiguration publisher = j.addPublisher(JUnitJobConfiguration.class); + publisher.setTestResults("*.xml"); + publisher.setRetainLogStandardOutputError(true); + + j.save(); + Build build = j.startBuild().shouldSucceed(); + j.visit("/job/" + j.name + "/1/testReport/(root)/JUnit/testScore_0_/"); + TestDetail testDetail = new TestDetail(build); + + assertThat(testDetail.getStandardOutput()).isPresent(); + assertThat(testDetail.getStandardOutput().get()).doesNotContain("truncated"); + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/ProjectSummaryTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/ProjectSummaryTest.java new file mode 100644 index 000000000..cc04e8460 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/ProjectSummaryTest.java @@ -0,0 +1,61 @@ +package io.jenkins.plugins.analysis.junit; + +import org.json.JSONException; +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static io.jenkins.plugins.analysis.junit.JUnitProjectSummaryAssert.*; +import static org.assertj.core.api.AssertionsForClassTypes.*; + +/** + * Tests the published results of JUnit tests on the job summary page. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +public class ProjectSummaryTest extends AbstractJUnitTest { + + /** + * Verifies correct information at latest result link. + */ + @Test + public void verifyLatestTestResultsInformation() throws JSONException { + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + lastBuild.job.open(); + + JUnitProjectSummary projectSummary = new JUnitProjectSummary(lastBuild); + + assertThat(projectSummary) + .hasNumberOfFailures(4) + .hasFailureDifference(1); + + assertThat(projectSummary.getTitleText()) + .contains("4 failures") + .contains("+1"); + } + + /** + * Verifies correct information in the test result trend chart. + */ + @Test + public void verifyTrendChartForTwoConsecutiveBuilds() throws JSONException { + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + lastBuild.job.open(); + + JUnitProjectSummary projectSummary = new JUnitProjectSummary(lastBuild); + + TestUtils.assertElementInCollection(projectSummary.getBuildChartEntries(), + buildChartEntry -> buildChartEntry.getBuildId() == 1 + && buildChartEntry.getNumberOfFailedTests() == 3 + && buildChartEntry.getNumberOfPassedTests() == 2 + && buildChartEntry.getNumberOfSkippedTests() == 0, + buildChartEntry -> buildChartEntry.getBuildId() == 2 + && buildChartEntry.getNumberOfFailedTests() == 4 + && buildChartEntry.getNumberOfPassedTests() == 1 + && buildChartEntry.getNumberOfSkippedTests() == 0); + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/README.md b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/README.md deleted file mode 100644 index ac9b2e23c..000000000 --- a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Please delete me after you added some files here. -The files to be added here are tests for UI testing \ No newline at end of file diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/TestDetailTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/TestDetailTest.java new file mode 100644 index 000000000..ba7869e59 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/TestDetailTest.java @@ -0,0 +1,152 @@ +package io.jenkins.plugins.analysis.junit; + +import java.util.Arrays; + +import org.junit.Test; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.junit.WithPlugins; +import org.jenkinsci.test.acceptance.po.Build; + +import io.jenkins.plugins.analysis.junit.util.TestUtils; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +/** + * Tests the the detail view of a failed JUnit test. + * + * @author Michael Müller + * @author Nikolas Paripovic + */ +@WithPlugins("junit") +public class TestDetailTest extends AbstractJUnitTest { + + /** + * Verifies detail information of passed test are displayed correctly. + */ + @Test + public void verifyTestDetailWhenFailed() { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/success/com.simple.project.AppTest.txt", "/success/TEST-com.simple.project.AppTest.xml"), "SUCCESS"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + TestDetail testDetail = buildSummary.openBuildTestResults() + .openTestResultsByPackage("com.simple.project") + .openTestResultsByClass("AppTest") + .openTestDetail("testApp"); + + assertThat(testDetail.getTitle()).contains("Passed"); + assertThat(testDetail.getSubTitle()).contains("com.simple.project.AppTest.testApp"); + + assertThat(testDetail.getErrorMessage()).isEmpty(); + assertThat(testDetail.getStackTrace()).isEmpty(); + assertThat(testDetail.getStandardOutput()).isEmpty(); + } + + /** + * Verifies detail information with standard output of passed test are displayed correctly. + */ + @Test + public void verifyTestDetailWhenFailedWithStandardOutput() { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/success/com.simple.project.AppTest.txt", "/success/junit-with-long-output.xml"), "SUCCESS"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + TestDetail testDetail = buildSummary.openBuildTestResults() + .openTestResultsByPackage("(root)") + .openTestResultsByClass("JUnit") + .openTestDetail("testScore[0]"); + + assertThat(testDetail.getTitle()).contains("Passed"); + assertThat(testDetail.getSubTitle()).contains("JUnit.testScore[0]"); + + assertThat(testDetail.getErrorMessage()).isEmpty(); + assertThat(testDetail.getStackTrace()).isEmpty(); + + assertThat(testDetail.getStandardOutput()).isPresent(); + assertThat(testDetail.getStandardOutput().get()).contains("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore"); + } + + /** + * Verifies detail information of passed test are displayed correctly. + */ + @Test + public void verifyTestDetailWhenPassed() { + Build build = TestUtils.createFreeStyleJobAndRunBuild( + this, + Arrays.asList("/parameterized/junit.xml", "/parameterized/testng.xml"), "UNSTABLE"); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(build); + TestDetail testDetail = buildSummary.openTestDetailView("JUnit.testScore[0]"); + + assertThat(testDetail.getTitle()).contains("Failed"); + assertThat(testDetail.getStandardOutput()).isEmpty(); + assertThat(testDetail.getSubTitle()).contains("JUnit.testScore[0]"); + + assertThat(testDetail.getErrorMessage()).isPresent(); + assertThat(testDetail.getErrorMessage()).get().isEqualTo("expected:<42> but was:<0>"); + + assertThat(testDetail.getStackTrace()).isPresent(); + assertThat(testDetail.getStackTrace()).get().isEqualTo( + "java.lang.AssertionError: expected:<42> but was:<0>\n" + + " at org.junit.Assert.fail(Assert.java:88)\n" + + " at org.junit.Assert.failNotEquals(Assert.java:743)\n" + + " at org.junit.Assert.assertEquals(Assert.java:118)\n" + + " at org.junit.Assert.assertEquals(Assert.java:555)\n" + + " at org.junit.Assert.assertEquals(Assert.java:542)\n" + + " at JUnit.testScore(JUnitTC.java:21)\n" + + " at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + + " at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)\n" + + " at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + + " at java.lang.reflect.Method.invoke(Method.java:606)\n" + + " at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)\n" + + " at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)\n" + + " at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)\n" + + " at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)\n" + + " at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)\n" + + " at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)\n" + + " at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)\n" + + " at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\n" + + " at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\n" + + " at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\n" + + " at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)\n" + + " at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\n" + + " at org.junit.runners.ParentRunner.run(ParentRunner.java:309)\n" + + " at org.junit.runners.Suite.runChild(Suite.java:127)\n" + + " at org.junit.runners.Suite.runChild(Suite.java:26)\n" + + " at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)\n" + + " at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)\n" + + " at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)\n" + + " at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)\n" + + " at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)\n" + + " at org.junit.runners.ParentRunner.run(ParentRunner.java:309)\n" + + " at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)\n" + + " at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)\n" + + " at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)\n" + + " at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)\n" + + " at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)\n" + + " at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)"); + + } + + /** + * Verifies run unit test has status "Regression" when this test failed after success in previous build. + */ + @Test + public void verifiesTestHasStatusRegressionWhenConsecutiveBuildFailed() { + + Build lastBuild = TestUtils.createTwoBuildsWithIncreasedTestFailures(this); + + JUnitBuildSummary buildSummary = new JUnitBuildSummary(lastBuild); + TestDetail testDetail = buildSummary + .openBuildTestResults() + .openTestResultsByPackage("com.another.simple.project") + .openTestResultsByClass("ApplicationTest") + .openTestDetail("testApplicationSuccess"); + + assertThat(testDetail.getTitle()).contains("Regression"); + } + +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/FixedCopyJobDecorator.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/FixedCopyJobDecorator.java new file mode 100644 index 000000000..be4c9c903 --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/FixedCopyJobDecorator.java @@ -0,0 +1,114 @@ +package io.jenkins.plugins.analysis.junit.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.SystemUtils; +import org.codehaus.plexus.util.Base64; + +import org.jenkinsci.test.acceptance.junit.Resource; +import org.jenkinsci.test.acceptance.po.Job; + +/** + * Wrapper around a {@link Job} with slightly modified behaviour in methods {@link #copyResource(Resource)}, {@link #copyResource(String)} and {@link #copyResource(Resource, String)}. + * Without these modifications, the following Jenkins error occurs when resources are copied by {@code xcopy} using Windows OS: {@Code Test reports were found but none of them are new. Did tests run?}. + * The solution applied here is to execute another command {@code touch} to update the timestamp and fix the error. + * @author Nikolas Paripovic + */ +public class FixedCopyJobDecorator { + + private final Job job; + + /** + * Custom constructor. Creates object. + * @param job the jenkins job to apply the fix + */ + public FixedCopyJobDecorator(Job job) { + this.job = job; + } + + /** + * Gets the origin object with default behaviour. + * If called {@link Job#copyResource(Resource)}, {@link Job#copyResource(String)} or {@link Job#copyResource(Resource, String)} on this object, no fix (described in this class description) is applied here. + * @return the origin object + */ + public Job getJob() { + return job; + } + + /** + * Slightly modified behaviour of {@link Job#copyResource(Resource, String)} with the bugfix described in this class description. + * @param resource the resource to copy + * @param fileName the name of the resource to copy + */ + public void copyResource(Resource resource, String fileName) { + if (SystemUtils.IS_OS_WINDOWS) { + job.addBatchStep(copyResourceBatch(resource)); + } + else { + job.addShellStep(copyResourceShell(resource, fileName)); + } + } + + /** + * Slightly modified behaviour of {@link Job#copyResource(Resource)} with the bugfix described in this class description. + * @param resource the resource to copy + */ + public void copyResource(Resource resource) { + copyResource(resource, resource.getName()); + } + + /** + * Slightly modified behaviour of {@link Job#copyResource(String)} with the bugfix described in this class description. + * @param resourcePath the resource to copy + */ + public void copyResource(String resourcePath) { + job.ensureConfigPage(); + final Resource res = job.resource(resourcePath); + //decide whether to utilize copyResource or copyDir + if (res.asFile().isDirectory()) { + job.copyDir(res); + } + else { + copyResource(res); + } + } + + private String copyResourceBatch(Resource resource) { + String path = resource.url.getPath(); + if (path.startsWith("/")) { + path = path.substring(1); + } + path = path.replace("/", "\\"); + return xCopy(path, ".") + "\n" + touch(resource.getName()); + } + + private String touch(final String source) { + return "touch " + source; + } + + private String xCopy(final String source, final String destination) { + return "xcopy " + source + " " + destination + " /E /Y"; + } + + private String copyResourceShell(Resource resource, String fileName) { + try (InputStream in = resource.asInputStream()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try (OutputStream gz = new GZIPOutputStream(out)) { + IOUtils.copy(in, gz); + } + + // fileName can include path portion like foo/bar/zot + return String.format("(mkdir -p %1$s || true) && rm -r %1$s && base64 --decode << ENDOFFILE | gunzip > %1$s \n%2$s\nENDOFFILE", + fileName, new String(Base64.encodeBase64Chunked(out.toByteArray()))); + } + catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/TestUtils.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/TestUtils.java new file mode 100644 index 000000000..424dbf89c --- /dev/null +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/junit/util/TestUtils.java @@ -0,0 +1,99 @@ +package io.jenkins.plugins.analysis.junit.util; + +import java.lang.annotation.ElementType; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.jenkinsci.test.acceptance.junit.AbstractJUnitTest; +import org.jenkinsci.test.acceptance.po.Build; +import org.jenkinsci.test.acceptance.po.FreeStyleJob; +import org.jenkinsci.test.acceptance.po.JUnitPublisher; +import org.jenkinsci.test.acceptance.po.Job; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * Util methods for tests. + */ +public class TestUtils { + + /** + * Creates a freestyle Job with resources and runs build with expected build result. + * + * @param abstractJUnitTestBaseClass the caller's test class + * @param resourcePaths resource paths of test result reports + * @param expectedBuildResult expected build results for assertion + * @return created and ran build + */ + public static Build createFreeStyleJobAndRunBuild(AbstractJUnitTest abstractJUnitTestBaseClass, + List resourcePaths, String expectedBuildResult) { + Build build = getCreatedFreeStyleJobWithResources(abstractJUnitTestBaseClass, resourcePaths).startBuild(); + assertThat(build.getResult()).isEqualTo(expectedBuildResult); + build.open(); + return build; + } + + /** + * Creates a freestyle Job with resources. + * + * @param abstractJUnitTestBaseClass the caller's test class + * @param resourcePaths resource paths of test result reports + * @return created freestyle job. + */ + public static Job getCreatedFreeStyleJobWithResources(AbstractJUnitTest abstractJUnitTestBaseClass, + List resourcePaths) { + FreeStyleJob j = abstractJUnitTestBaseClass.jenkins.jobs.create(); + FixedCopyJobDecorator fixedCopyJob = new FixedCopyJobDecorator(j); + fixedCopyJob.getJob().configure(); + for (String resourcePath : resourcePaths) { + fixedCopyJob.copyResource(abstractJUnitTestBaseClass.resource(resourcePath)); + } + JUnitPublisher publisher = fixedCopyJob.getJob().addPublisher(JUnitPublisher.class); + publisher.testResults.set("*.xml"); + + fixedCopyJob.getJob().save(); + return fixedCopyJob.getJob(); + } + + /** + * Creates a freestyle and runs two consecutive builds with different test result report which increases failure count + * in the second build. + * + * @param abstractJUnitTestBaseClass the caller's test class + * @return second build + */ + public static Build createTwoBuildsWithIncreasedTestFailures(AbstractJUnitTest abstractJUnitTestBaseClass) { + FreeStyleJob j = abstractJUnitTestBaseClass.jenkins.jobs.create(); + FixedCopyJobDecorator fixedCopyJob = new FixedCopyJobDecorator(j); + fixedCopyJob.getJob().configure(); + fixedCopyJob.copyResource(abstractJUnitTestBaseClass.resource("/failure/three_failed_two_succeeded.xml")); + fixedCopyJob.copyResource(abstractJUnitTestBaseClass.resource("/failure/four_failed_one_succeeded.xml")); + fixedCopyJob.getJob().addPublisher(JUnitPublisher.class).testResults.set("three_failed_two_succeeded.xml"); + fixedCopyJob.getJob().save(); + fixedCopyJob.getJob().startBuild().shouldBeUnstable(); + + fixedCopyJob.getJob().configure(); + fixedCopyJob.getJob().editPublisher(JUnitPublisher.class, publisher -> publisher.testResults.set("four_failed_one_succeeded.xml")); + + fixedCopyJob.getJob().startBuild().shouldBeUnstable().openStatusPage(); + return fixedCopyJob.getJob().getLastBuild(); + } + + /** + * Asserts given predicates within the given collection. + * + * @param collection collection to be asserted + * @param predicates assertion criteria + * @param the type of elements in this collection + */ + public static void assertElementInCollection(Collection collection, + Predicate... predicates) { + assertThat(Stream.of(predicates).allMatch(predicate -> collection.stream() + .filter(predicate) + .findAny() + .isPresent())).isTrue(); + } +} diff --git a/ui-tests/src/test/resources/failure/TEST-com.simple.project.AppTest.xml b/ui-tests/src/test/resources/failure/TEST-com.simple.project.AppTest.xml new file mode 100644 index 000000000..dd42384cb --- /dev/null +++ b/ui-tests/src/test/resources/failure/TEST-com.simple.project.AppTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + junit.framework.AssertionFailedError + at junit.framework.Assert.fail(Assert.java:47) + at junit.framework.Assert.assertTrue(Assert.java:20) + at junit.framework.Assert.assertTrue(Assert.java:27) + at com.simple.project.AppTest.testApp(AppTest.java:36) + + + \ No newline at end of file diff --git a/ui-tests/src/test/resources/failure/com.simple.project.AppTest.txt b/ui-tests/src/test/resources/failure/com.simple.project.AppTest.txt new file mode 100644 index 000000000..7c6431a23 --- /dev/null +++ b/ui-tests/src/test/resources/failure/com.simple.project.AppTest.txt @@ -0,0 +1,11 @@ +------------------------------------------------------------------------------- +Test set: com.simple.project.AppTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.014 sec <<< FAILURE! +testApp(com.simple.project.AppTest) Time elapsed: 0.007 sec <<< FAILURE! +junit.framework.AssertionFailedError + at junit.framework.Assert.fail(Assert.java:47) + at junit.framework.Assert.assertTrue(Assert.java:20) + at junit.framework.Assert.assertTrue(Assert.java:27) + at com.simple.project.AppTest.testApp(AppTest.java:36) + diff --git a/ui-tests/src/test/resources/failure/four_failed_one_succeeded.xml b/ui-tests/src/test/resources/failure/four_failed_one_succeeded.xml new file mode 100644 index 000000000..62c76aaee --- /dev/null +++ b/ui-tests/src/test/resources/failure/four_failed_one_succeeded.xml @@ -0,0 +1,63 @@ + + + + junit.framework.AssertionFailedError + at junit.framework.Assert.fail(Assert.java:47) + at junit.framework.Assert.assertTrue(Assert.java:20) + at junit.framework.Assert.assertTrue(Assert.java:27) + at com.simple.project.AppTest.testApp(AppTest.java:36) + + + + + + + + + + + + + + + java.lang.AssertionError: expected:<42> but was:<0> + at org.junit.Assert.fail(Assert.java:88) + at org.junit.Assert.failNotEquals(Assert.java:743) + at org.junit.Assert.assertEquals(Assert.java:118) + at org.junit.Assert.assertEquals(Assert.java:555) + at org.junit.Assert.assertEquals(Assert.java:542) + at JUnit.testScore(JUnitTC.java:21) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.junit.runners.Suite.runChild(Suite.java:127) + at org.junit.runners.Suite.runChild(Suite.java:26) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + + + diff --git a/ui-tests/src/test/resources/failure/three_failed_two_succeeded.xml b/ui-tests/src/test/resources/failure/three_failed_two_succeeded.xml new file mode 100644 index 000000000..2dc566de1 --- /dev/null +++ b/ui-tests/src/test/resources/failure/three_failed_two_succeeded.xml @@ -0,0 +1,61 @@ + + + + junit.framework.AssertionFailedError + at junit.framework.Assert.fail(Assert.java:47) + at junit.framework.Assert.assertTrue(Assert.java:20) + at junit.framework.Assert.assertTrue(Assert.java:27) + at com.simple.project.AppTest.testApp(AppTest.java:36) + + + + + + + + + + + + + java.lang.AssertionError: expected:<42> but was:<0> + at org.junit.Assert.fail(Assert.java:88) + at org.junit.Assert.failNotEquals(Assert.java:743) + at org.junit.Assert.assertEquals(Assert.java:118) + at org.junit.Assert.assertEquals(Assert.java:555) + at org.junit.Assert.assertEquals(Assert.java:542) + at JUnit.testScore(JUnitTC.java:21) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.junit.runners.Suite.runChild(Suite.java:127) + at org.junit.runners.Suite.runChild(Suite.java:26) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + + + diff --git a/ui-tests/src/test/resources/parameterized/junit.xml b/ui-tests/src/test/resources/parameterized/junit.xml new file mode 100644 index 000000000..eb2aeab30 --- /dev/null +++ b/ui-tests/src/test/resources/parameterized/junit.xml @@ -0,0 +1,136 @@ + + + + + java.lang.AssertionError: expected:<42> but was:<0> + at org.junit.Assert.fail(Assert.java:88) + at org.junit.Assert.failNotEquals(Assert.java:743) + at org.junit.Assert.assertEquals(Assert.java:118) + at org.junit.Assert.assertEquals(Assert.java:555) + at org.junit.Assert.assertEquals(Assert.java:542) + at JUnit.testScore(JUnitTC.java:21) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.junit.runners.Suite.runChild(Suite.java:127) + at org.junit.runners.Suite.runChild(Suite.java:26) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + + + + + + + java.lang.AssertionError: expected:<42> but was:<1> + at org.junit.Assert.fail(Assert.java:88) + at org.junit.Assert.failNotEquals(Assert.java:743) + at org.junit.Assert.assertEquals(Assert.java:118) + at org.junit.Assert.assertEquals(Assert.java:555) + at org.junit.Assert.assertEquals(Assert.java:542) + at JUnit.testScore(JUnitTC.java:21) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.junit.runners.Suite.runChild(Suite.java:127) + at org.junit.runners.Suite.runChild(Suite.java:26) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + + + + + + + java.lang.AssertionError: expected:<42> but was:<2> + at org.junit.Assert.fail(Assert.java:88) + at org.junit.Assert.failNotEquals(Assert.java:743) + at org.junit.Assert.assertEquals(Assert.java:118) + at org.junit.Assert.assertEquals(Assert.java:555) + at org.junit.Assert.assertEquals(Assert.java:542) + at JUnit.testScore(JUnitTC.java:21) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.junit.runners.Suite.runChild(Suite.java:127) + at org.junit.runners.Suite.runChild(Suite.java:26) + at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) + at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) + at org.junit.runners.ParentRunner.run(ParentRunner.java:309) + at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) + at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) + at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) + + + + + + diff --git a/ui-tests/src/test/resources/parameterized/testng.xml b/ui-tests/src/test/resources/parameterized/testng.xml new file mode 100644 index 000000000..2d27c3eca --- /dev/null +++ b/ui-tests/src/test/resources/parameterized/testng.xml @@ -0,0 +1,142 @@ + + + + + + but was:<2> + at org.testng.AssertJUnit.fail(AssertJUnit.java:59) + at org.testng.AssertJUnit.failNotEquals(AssertJUnit.java:364) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:80) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:245) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:252) + at TestNG.testScore(TestNG.java:12) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80) + at org.testng.internal.Invoker.invokeMethod(Invoker.java:702) + at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:894) + at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1219) + at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127) + at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111) + at org.testng.TestRunner.privateRun(TestRunner.java:768) + at org.testng.TestRunner.run(TestRunner.java:617) + at org.testng.SuiteRunner.runTest(SuiteRunner.java:334) + at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329) + at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291) + at org.testng.SuiteRunner.run(SuiteRunner.java:240) + at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53) + at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:87) + at org.testng.TestNG.runSuitesSequentially(TestNG.java:1188) + at org.testng.TestNG.runSuitesLocally(TestNG.java:1113) + at org.testng.TestNG.run(TestNG.java:1025) + at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:77) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:189) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:105) + at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:117) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208) + at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158) + at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95) +]]> + + + + + but was:<0> + at org.testng.AssertJUnit.fail(AssertJUnit.java:59) + at org.testng.AssertJUnit.failNotEquals(AssertJUnit.java:364) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:80) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:245) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:252) + at TestNG.testScore(TestNG.java:12) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80) + at org.testng.internal.Invoker.invokeMethod(Invoker.java:702) + at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:894) + at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1219) + at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127) + at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111) + at org.testng.TestRunner.privateRun(TestRunner.java:768) + at org.testng.TestRunner.run(TestRunner.java:617) + at org.testng.SuiteRunner.runTest(SuiteRunner.java:334) + at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329) + at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291) + at org.testng.SuiteRunner.run(SuiteRunner.java:240) + at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53) + at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:87) + at org.testng.TestNG.runSuitesSequentially(TestNG.java:1188) + at org.testng.TestNG.runSuitesLocally(TestNG.java:1113) + at org.testng.TestNG.run(TestNG.java:1025) + at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:77) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:189) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:105) + at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:117) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208) + at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158) + at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95) +]]> + + + + + but was:<1> + at org.testng.AssertJUnit.fail(AssertJUnit.java:59) + at org.testng.AssertJUnit.failNotEquals(AssertJUnit.java:364) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:80) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:245) + at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:252) + at TestNG.testScore(TestNG.java:12) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80) + at org.testng.internal.Invoker.invokeMethod(Invoker.java:702) + at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:894) + at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1219) + at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127) + at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111) + at org.testng.TestRunner.privateRun(TestRunner.java:768) + at org.testng.TestRunner.run(TestRunner.java:617) + at org.testng.SuiteRunner.runTest(SuiteRunner.java:334) + at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329) + at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291) + at org.testng.SuiteRunner.run(SuiteRunner.java:240) + at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53) + at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:87) + at org.testng.TestNG.runSuitesSequentially(TestNG.java:1188) + at org.testng.TestNG.runSuitesLocally(TestNG.java:1113) + at org.testng.TestNG.run(TestNG.java:1025) + at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:77) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:189) + at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:105) + at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:117) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:606) + at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208) + at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158) + at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86) + at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153) + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95) +]]> + + + diff --git a/ui-tests/src/test/resources/success/TEST-com.simple.project.AppTest.xml b/ui-tests/src/test/resources/success/TEST-com.simple.project.AppTest.xml new file mode 100644 index 000000000..2ce9ec0f9 --- /dev/null +++ b/ui-tests/src/test/resources/success/TEST-com.simple.project.AppTest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui-tests/src/test/resources/success/com.simple.project.AppTest.txt b/ui-tests/src/test/resources/success/com.simple.project.AppTest.txt new file mode 100644 index 000000000..74de4a3a1 --- /dev/null +++ b/ui-tests/src/test/resources/success/com.simple.project.AppTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.simple.project.AppTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec diff --git a/ui-tests/src/test/resources/success/junit-with-long-output.xml b/ui-tests/src/test/resources/success/junit-with-long-output.xml new file mode 100644 index 000000000..ee65c9fc6 --- /dev/null +++ b/ui-tests/src/test/resources/success/junit-with-long-output.xml @@ -0,0 +1,31 @@ + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + + Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + + Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + + Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + + Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + + At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. + + Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + + Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + + Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + + Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo + + + + +
${it.childTitle}${%Duration}${%Fail}(${%diff})${%Skip}(${%diff})${%Pass}(${%diff})${%Total}(${%diff})