├── .github
└── workflows
│ ├── build-tests.yml
│ └── workflow-action.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── flake-guard-app
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc.js
├── Dockerfile
├── babel.config.js
├── dataobj.json
├── global.d.ts
├── jest.config.js
├── jest.setup.ts
├── package-lock.json
├── package.json
├── src
│ ├── client
│ │ ├── app.test.tsx
│ │ ├── app.tsx
│ │ ├── assets
│ │ │ ├── advantage1.png
│ │ │ ├── advantage2.png
│ │ │ ├── advantage3.png
│ │ │ ├── advantage4.png
│ │ │ ├── arrow.png
│ │ │ ├── ashley.png
│ │ │ ├── brendan.png
│ │ │ ├── condensed-logo.png
│ │ │ ├── download.png
│ │ │ ├── flakeguard-logo-white-background.png
│ │ │ ├── github-logo.png
│ │ │ ├── github-mark-white.png
│ │ │ ├── github-mark.png
│ │ │ ├── graphs.png
│ │ │ ├── linkedinlogo.png
│ │ │ ├── logo.png
│ │ │ ├── logo_with_background.png
│ │ │ ├── npm.png
│ │ │ ├── paloma.png
│ │ │ ├── signout.png
│ │ │ ├── tommy.png
│ │ │ └── will.png
│ │ ├── docs
│ │ │ ├── DocPage.tsx
│ │ │ ├── FAQ.tsx
│ │ │ └── faq.test.tsx
│ │ ├── flakeRiskSign
│ │ │ ├── Analytics
│ │ │ │ ├── assertion-failures-percent.tsx
│ │ │ │ ├── flake-percentage.tsx
│ │ │ │ └── overall-failed-percentage.tsx
│ │ │ └── FlakeRiskSign
│ │ │ │ ├── FlakeRiskArrow.tsx
│ │ │ │ ├── FlakeRiskContainer.tsx
│ │ │ │ ├── FlakeRiskSign.tsx
│ │ │ │ └── FlakeRiskSlice.tsx
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── landingPage
│ │ │ ├── LandingPage.tsx
│ │ │ └── components
│ │ │ │ ├── Advantages.tsx
│ │ │ │ ├── Footer.tsx
│ │ │ │ ├── LoginButton.tsx
│ │ │ │ ├── MeetTheTeam.tsx
│ │ │ │ ├── NavBar.tsx
│ │ │ │ └── advantages.test.tsx
│ │ ├── services
│ │ │ └── index.tsx
│ │ ├── styles
│ │ │ ├── advantages.css
│ │ │ ├── dashboard.css
│ │ │ ├── dashboard
│ │ │ │ ├── charts.css
│ │ │ │ ├── newDashboard.css
│ │ │ │ └── sidebar.css
│ │ │ ├── decisionPage.css
│ │ │ ├── docs.css
│ │ │ ├── footer.css
│ │ │ ├── header.css
│ │ │ ├── landingPage.css
│ │ │ ├── riskSign.css
│ │ │ ├── styles.css
│ │ │ ├── tailwind-output.css
│ │ │ └── tempDash.css
│ │ ├── supabaseClient.ts
│ │ ├── tempDash
│ │ │ ├── DecisionPage.tsx
│ │ │ ├── TempDashboard.tsx
│ │ │ └── components
│ │ │ │ ├── AssertionsGraph.tsx
│ │ │ │ ├── DisplayErrors.tsx
│ │ │ │ ├── Flakiness.tsx
│ │ │ │ └── Summary.tsx
│ │ ├── types.ts
│ │ ├── userDash
│ │ │ ├── UserDashboard.tsx
│ │ │ ├── components
│ │ │ │ ├── Sidebar.tsx
│ │ │ │ ├── bar
│ │ │ │ │ ├── BarChart.tsx
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── calendar
│ │ │ │ │ ├── Calendar.tsx
│ │ │ │ │ └── data.ts
│ │ │ │ ├── duration
│ │ │ │ │ └── Duration.tsx
│ │ │ │ ├── errorsDetails
│ │ │ │ │ └── ErrorsDetails.tsx
│ │ │ │ ├── line
│ │ │ │ │ └── LineChart.tsx
│ │ │ │ └── pie
│ │ │ │ │ └── PieChart.tsx
│ │ │ ├── contexts
│ │ │ │ └── ResultContext.tsx
│ │ │ └── pages
│ │ │ │ ├── codeCoverage
│ │ │ │ └── CodeCoverage.tsx
│ │ │ │ ├── flakyTests
│ │ │ │ └── FlakyTests.tsx
│ │ │ │ └── history
│ │ │ │ └── History.tsx
│ │ └── utilities
│ │ │ ├── barchartDataParser.ts
│ │ │ ├── displayErrorsDataParser.ts
│ │ │ ├── flakyDataParser.ts
│ │ │ └── lineChartParser.ts
│ └── server
│ │ ├── controllers
│ │ ├── cacheController.test.js
│ │ ├── cacheController.ts
│ │ ├── dbController.test.js
│ │ ├── dbController.ts
│ │ ├── npmController.test.js
│ │ ├── npmController.ts
│ │ ├── tempCache.test.js
│ │ ├── urlController.test.js
│ │ └── urlController.ts
│ │ ├── db
│ │ └── db.ts
│ │ ├── errors
│ │ ├── CustomError.ts
│ │ ├── errorHandler.ts
│ │ └── errors.ts
│ │ ├── routes
│ │ ├── resultsRouter.ts
│ │ ├── tempDashRouter.ts
│ │ └── userDashRouter.ts
│ │ ├── server.ts
│ │ └── tempCache.ts
├── tailwind.config.js
├── tsconfig.json
└── webpack.config.js
└── flake-guard-npm
├── .gitignore
├── README.md
├── default.json
├── index.js
├── loadConfig.js
└── package.json
/.github/workflows/build-tests.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: build-tests
5 |
6 | on:
7 | pull_request:
8 | branches: [ "main" ]
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [20.x]
18 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v3
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | cache: 'npm'
27 | - run: npm ci
28 | - run: npm run build --if-present
29 | - run: npm test
30 |
--------------------------------------------------------------------------------
/.github/workflows/workflow-action.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI Workflow
4 |
5 | # Controls when the workflow will run
6 | on: workflow_dispatch
7 | # Triggers the workflow on push or pull request events but only for the "dev" branch
8 | # push:
9 | # branches: [ "dev" ]
10 | # pull_request:
11 | # branches: [ "dev" ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | # workflow_dispatch: workflow_dispatch
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | first-job:
19 | runs-on: ubuntu-latest
20 | # Steps represent a sequence of tasks that will be executed as part of the job
21 | steps:
22 | # Runs a single command using the runners shell
23 | - name: Print greeting
24 | run: |
25 | echo "Hello World!"
26 | echo "Second line!
27 | - name: Print goodbye
28 | run: echo "Done - goodbye!"
29 |
30 | # This workflow contains a single job called "build"
31 | # build:
32 | # # The type of runner that the job will run on
33 | # runs-on: ubuntu-latest
34 |
35 |
36 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
37 | # - uses: actions/checkout@v4
38 |
39 |
40 | # # Runs a set of commands using the runners shell
41 | # - name: Run a multi-line script
42 | # run: |
43 | # echo Add other actions to build,
44 | # echo test, and deploy your project.
45 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | 🚀 Welcome to FlakeGuard contribution, we are thrillled to have you here! 🚀
2 |
3 | # How Do I submit A (Good) Pull Request?
4 | All pull requests will be reviewed by the authors and will either be
5 | 1. Denied
6 | 2. Denied with feedback
7 | 3. Approved contingent upon minor changes
8 | 4. Approved and merged
9 |
10 | ## Testing
11 | Please write Jest, or React Testing Library files to test the new code you create.
12 |
13 | ## Pull Request
14 | Please send a Pull Request to FlakeGuard with a clear list of what you've done (read more about [pull requests](https://support.github.com/)). When you send a pull request, we will love you forever if you include RSpec examples. We can always use more test coverage. Please follow our coding conventions (below) and make sure all of your commits are atomic (one feature per commit).
15 |
16 | Always write a clear log message for your commits, including a summary and a detailed paragraph about all changes and testing.
17 |
18 |
19 | # How Do I Submit A (Good) Bug Report?
20 | Bugs are tracked as [GitHub Issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues).
21 |
22 | 1. Create an issue and provide the following information by filling in the template.
23 | 2. Explain the problem and include additional details to help maintainers reproduce the problem:
24 |
25 | *Use a clear and descriptive title for the issue to identify the problem.*
26 |
27 | Describe the exact steps which reproduce the problem in as many details as possible.
28 | Provide specific examples to demonstrate the steps. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples.
29 | If you're providing snippets in the issue, use Markdown code blocks.
30 | Describe the behavior you observed after following the steps and point out what exactly is the problem with that behavior.
31 | Explain which behavior you expected to see instead and why.
32 |
33 | If the problem wasn't triggered by a specific action, describe what you were doing before the problem happened and share more information.
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | [MIT License]
2 |
3 | Copyright (c) 2024 Open Source FlakeGuard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 🚀 Welcome to flake guard! 🚀
4 | ### [Website](https://flakeguard.com/) | | [Npm](https://www.npmjs.com/package/flake-guard) | | [Medium](https://medium.com/@ashleyhannigan88/flake-guard-open-source-01431eb6ede3)
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ### FlakeGuard is a free, open-source software that allows developers to run Jest tests to automatically detect, report, and track flaky tests in software development.
21 | ---
22 | *Flaky test: a test that sometimes passes and sometimes fails for the same code, often due to nondeterministic factors like timing issues, network variability, or reliance on external systems.*
23 |
24 | **By identifying flaky tests, FlakeGuard helps users improve test assurances.**
25 |
26 | ## __Table of Contents__
27 | 1. [Demo](#Demo)
28 | 2. [Getting Started](#getting-started)
29 | 3. [How it works](#how-it-works)
30 | 4. [Tracked dashboard metrics](#tracked-dashboard-metrics)
31 | 5. [Authors](#authors)
32 | 6. [Contributing](#contributing)
33 |
34 | ---
35 | # Getting Started
36 |
37 | Install the FlakeGuard NPM package as a dev dependency by running the command
38 | ```npm i flake-guard --save-dev```
39 |
40 | To run FlakeGuard in your project, simply execute the command
41 | ```npx flake-guard ```
42 | . Change `` to the name of the test file that you want to examine.
43 |
44 | 👁️FlakeGuard will analyze your tests for flakiness by executing multiple test runs and analyzing the results. _The default number of test runs is 10_, but this can be adjusted as described below.
45 |
46 | In general, there is a time versus accuracy tradeoff. More test executions increase accuracy but reduce speed.
47 |
48 | ## Configuration:
49 | To adjust FlakeGuard configuration variables, you can create a file in the root directory of your project called
50 | fg.config.json
51 | . Below are the defaults, which can be overridden in your local 'fg.config.json' file.
52 |
53 |
54 | ```
55 | {
56 | runs: 10
57 | }
58 | ```
59 | For example, if you want to increase accuracy, you can increase test runs.
60 | ```
61 | {
62 | runs: 100
63 | }
64 | ```
65 |
66 | # How It Works
67 | Under the hood, the flake-guard npm package is automating repeated runs of your test file. It will do a parsing of the results locally to log an object in your terminal with all of your test assertions and their pass/fail metrics. It sends off the raw Jest results objects to the FlakeGuard server for further analysis which you can view at flakeguard.com.
68 |
69 |
70 | # Tracked Dashboard Metrics
71 |
72 |
73 |
74 | The flake-guard NPM package pairs with the FlakeGuard web application. After the package runs in the terminal, the user will have the option to press Enter to send the results to the FlakeGuard server and open the FlakeGuard app in the browser.
75 |
76 | The user will be directed to a page where they have the option to either view a one-time simplified version of the user dashboard, or log in via Github to view advanced metrics and save their data to view the evolution of their test suite over time.
77 |
78 | # Future Features and Contributors
79 | We welcome feedback, new ideas, and contributors!
80 |
81 | Some features next in line for development include:
82 |
83 | - Allowing users to organize their stored results by filename
84 | - Incorporating Jest's code coverage metrics to visualize test suite coverage metrics and track changes over time
85 | - A history page where users can review previous results individually
86 | - Further tools to help users mitigate test flake, such as pinpointing test failure points and generating potential solutions
87 |
88 | ---
89 | # Authors
90 | | Name | Connect with Us |
91 | | ------------- |:-------------:|
92 | | Ashley Hannigan | [LinkedIn](https://www.linkedin.com/in/ashley-hannigan-88-/) `-` [Github](https://github.com/ashhannigan)
93 | | Brendan Xiong | [LinkedIn](https://www.linkedin.com/in/brendanxiong/) `-` [Github](https://github.com/brendanxiong)
94 | | Tommy Martinez | [LinkedIn](https://www.linkedin.com/in/tommy-martinez/) `-` [Github](https://github.com/tmm150)
95 | | Paloma Reynolds | [LinkedIn](https://www.linkedin.com/in/palomareynolds/) `-` [Github](https://github.com/palomareynolds)
96 | | Will Suto | [LinkedIn](https://www.linkedin.com/in/willsuto/) `-` [Github](https://github.com/willsuto)
97 |
98 | # Contributing
99 | [CONTRIBUTING.md](CONTRIBUTING.md)
100 |
--------------------------------------------------------------------------------
/flake-guard-app/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | insert_final_newline = true
9 |
--------------------------------------------------------------------------------
/flake-guard-app/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 |
--------------------------------------------------------------------------------
/flake-guard-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/gts/",
3 | "rules": {
4 | "node/no-unpublished-require": [
5 | "error",
6 | {
7 | "allowModules": [
8 | "html-webpack-plugin",
9 | "mini-css-extract-plugin",
10 | "workbox-webpack-plugin"
11 | ]
12 | }
13 | ]
14 | },
15 | "plugins": ["react", "@typescript-eslint", "prettier", "jest"],
16 | "settings": {
17 | "react": {
18 | "version": "detect"
19 | }
20 | },
21 | "overrides": [
22 | // Different options based on file extensions.
23 | {
24 | "parser": "babel-eslint",
25 | "files": ["**.js"]
26 | },
27 | {
28 | "parser": "@typescript-eslint/parser",
29 | "files": ["**.ts"]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/flake-guard-app/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | build/
4 | coverage/
5 | dist/
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 | *.lcov
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Microbundle cache
63 | .rpt2_cache/
64 | .rts2_cache_cjs/
65 | .rts2_cache_es/
66 | .rts2_cache_umd/
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # Output of 'npm pack'
72 | *.tgz
73 |
74 | # Yarn Integrity file
75 | .yarn-integrity
76 |
77 | # dotenv environment variables file
78 | .env
79 | .env.test
80 |
81 | # parcel-bundler cache (https://parceljs.org/)
82 | .cache
83 |
84 | # Next.js build output
85 | .next
86 |
87 | # Nuxt.js build / generate output
88 | .nuxt
89 |
90 | # Gatsby files
91 | .cache/
92 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
93 | # https://nextjs.org/blog/next-9-1#public-directory-support
94 | # public
95 |
96 | # vuepress build output
97 | .vuepress/dist
98 |
99 | # Serverless directories
100 | .serverless/
101 |
102 | # FuseBox cache
103 | .fusebox/
104 |
105 | # DynamoDB Local files
106 | .dynamodb/
107 |
108 | # TernJS port file
109 | .tern-port
110 |
111 | # VSCode files
112 | sapling/out
113 | sapling/dist
114 | sapling/node_modules
115 | sapling/.vscode-test/
116 | *.vsix
117 |
--------------------------------------------------------------------------------
/flake-guard-app/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ...require('gts/.prettierrc.json')
3 | }
4 |
--------------------------------------------------------------------------------
/flake-guard-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20.12
2 |
3 | WORKDIR /usr/src/app
4 |
5 | COPY . /usr/src/app/
6 |
7 | RUN npm install
8 | RUN npm install -g ts-node
9 | RUN npm run build
10 |
11 | EXPOSE 3000
12 |
13 | ENTRYPOINT ["ts-node", "./src/server/server.ts"]
14 |
--------------------------------------------------------------------------------
/flake-guard-app/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | // ['@babel/preset-react', {runtime: 'automatic'}],
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/flake-guard-app/dataobj.json:
--------------------------------------------------------------------------------
1 | [{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":0,"numPassedTests":1,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":2,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1717621230000,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Page loading","Initial page load"],"duration":772,"failureDetails":[],"failureMessages":[],"fullName":"Page loading Initial page load loads page successfully","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"loads page successfully"},{"ancestorTitles":["Page functionality","State interactions"],"duration":753,"failureDetails":[{}],"failureMessages":["Error: No element found for selector: new-location\n at assert (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)"],"fullName":"Page functionality State interactions can add a new market","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"status":"failed","title":"can add a new market"}],"endTime":1717621233176,"message":" ● Page functionality › State interactions › can add a new market\n\n No element found for selector: new-location\n\n at assert (node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n","name":"/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/__tests__/puppeteer2.js","startTime":1717621230533,"status":"failed","summary":""}],"wasInterrupted":false},{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":0,"numPassedTests":1,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":2,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1717621233753,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Page loading","Initial page load"],"duration":782,"failureDetails":[],"failureMessages":[],"fullName":"Page loading Initial page load loads page successfully","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"loads page successfully"},{"ancestorTitles":["Page functionality","State interactions"],"duration":748,"failureDetails":[{}],"failureMessages":["Error: No element found for selector: new-location\n at assert (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)"],"fullName":"Page functionality State interactions can add a new market","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"status":"failed","title":"can add a new market"}],"endTime":1717621236808,"message":" ● Page functionality › State interactions › can add a new market\n\n No element found for selector: new-location\n\n at assert (node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n","name":"/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/__tests__/puppeteer2.js","startTime":1717621234230,"status":"failed","summary":""}],"wasInterrupted":false},{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":0,"numPassedTests":1,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":2,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1717621237390,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Page loading","Initial page load"],"duration":798,"failureDetails":[],"failureMessages":[],"fullName":"Page loading Initial page load loads page successfully","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"loads page successfully"},{"ancestorTitles":["Page functionality","State interactions"],"duration":778,"failureDetails":[{}],"failureMessages":["Error: No element found for selector: new-location\n at assert (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)"],"fullName":"Page functionality State interactions can add a new market","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"status":"failed","title":"can add a new market"}],"endTime":1717621240491,"message":" ● Page functionality › State interactions › can add a new market\n\n No element found for selector: new-location\n\n at assert (node_modules/puppeteer/src/common/assert.ts:27:11)\n at DOMWorld.focus (node_modules/puppeteer/src/common/DOMWorld.ts:566:11)\n","name":"/Users/willsuto/Desktop/Codesmith/Coursework/OSP/Test App/__tests__/puppeteer2.js","startTime":1717621237865,"status":"failed","summary":""}],"wasInterrupted":false}]
--------------------------------------------------------------------------------
/flake-guard-app/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css' {
2 | const content: {[className: string]: string};
3 | export default content;
4 | }
5 | declare module '*.png' {
6 | const value: string;
7 | export default value;
8 | }
9 |
10 | declare module '*.jpg' {
11 | const value: string;
12 | export default value;
13 | }
14 |
15 | declare module '*.gif' {
16 | const value: string;
17 | export default value;
18 | }
19 |
20 | declare module '*.svg' {
21 | const value: string;
22 | export default value;
23 | }
24 |
25 | declare module '*.woff' {
26 | const value: string;
27 | export default value;
28 | }
29 |
30 | declare module '*.woff2' {
31 | const value: string;
32 | export default value;
33 | }
34 |
35 | declare module '*.eot' {
36 | const value: string;
37 | export default value;
38 | }
39 |
40 | declare module '*.ttf' {
41 | const value: string;
42 | export default value;
43 | }
44 |
--------------------------------------------------------------------------------
/flake-guard-app/jest.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * For a detailed explanation regarding each configuration property, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | /** @type {import('jest').Config} */
7 | const config = {
8 | // All imported modules in your tests should be mocked automatically
9 | // automock: false,
10 |
11 | // Stop running tests after `n` failures
12 | // bail: 0,
13 |
14 | // The directory where Jest should store its cached dependency information
15 | // cacheDirectory: "/private/var/folders/52/mww99m6x1qs17jmf0g9d5p040000gn/T/jest_dx",
16 |
17 | // Automatically clear mock calls, instances, contexts and results before every test
18 | clearMocks: true,
19 |
20 | // Indicates whether the coverage information should be collected while executing the test
21 | collectCoverage: true,
22 |
23 | // An array of glob patterns indicating a set of files for which coverage information should be collected
24 | // collectCoverageFrom: undefined,
25 |
26 | // The directory where Jest should output its coverage files
27 | coverageDirectory: 'coverage',
28 |
29 | // An array of regexp pattern strings used to skip coverage collection
30 | // coveragePathIgnorePatterns: [
31 | // "/node_modules/"
32 | // ],
33 |
34 | // Indicates which provider should be used to instrument code for coverage
35 | coverageProvider: 'v8',
36 |
37 | // A list of reporter names that Jest uses when writing coverage reports
38 | // coverageReporters: [
39 | // "json",
40 | // "text",
41 | // "lcov",
42 | // "clover"
43 | // ],
44 |
45 | // An object that configures minimum threshold enforcement for coverage results
46 | // coverageThreshold: undefined,
47 |
48 | // A path to a custom dependency extractor
49 | // dependencyExtractor: undefined,
50 |
51 | // Make calling deprecated APIs throw helpful error messages
52 | // errorOnDeprecated: false,
53 |
54 | // The default configuration for fake timers
55 | // fakeTimers: {
56 | // "enableGlobally": false
57 | // },
58 |
59 | // Force coverage collection from ignored files using an array of glob patterns
60 | // forceCoverageMatch: [],
61 |
62 | // A path to a module which exports an async function that is triggered once before all test suites
63 | // globalSetup: undefined,
64 |
65 | // A path to a module which exports an async function that is triggered once after all test suites
66 | // globalTeardown: undefined,
67 |
68 | // A set of global variables that need to be available in all test environments
69 | // globals: {},
70 |
71 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
72 | // maxWorkers: "50%",
73 |
74 | // An array of directory names to be searched recursively up from the requiring module's location
75 | // moduleDirectories: [
76 | // "node_modules"
77 | // ],
78 |
79 | // An array of file extensions your modules use
80 | // moduleFileExtensions: [
81 | // "js",
82 | // "mjs",
83 | // "cjs",
84 | // "jsx",
85 | // "ts",
86 | // "tsx",
87 | // "json",
88 | // "node"
89 | // ],
90 |
91 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
92 | // moduleNameMapper: {},
93 |
94 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
95 | // modulePathIgnorePatterns: [],
96 |
97 | // Activates notifications for test results
98 | // notify: false,
99 |
100 | // An enum that specifies notification mode. Requires { notify: true }
101 | // notifyMode: "failure-change",
102 |
103 | // A preset that is used as a base for Jest's configuration
104 | // preset: 'ts-jest',
105 |
106 | // Run tests from one or more projects
107 | // projects: undefined,
108 |
109 | // Use this configuration option to add custom reporters to Jest
110 | // reporters: undefined,
111 |
112 | // Automatically reset mock state before every test
113 | // resetMocks: false,
114 |
115 | // Reset the module registry before running each individual test
116 | // resetModules: false,
117 |
118 | // A path to a custom resolver
119 | // resolver: undefined,
120 |
121 | // Automatically restore mock state and implementation before every test
122 | // restoreMocks: false,
123 |
124 | // The root directory that Jest should scan for tests and modules within
125 | // rootDir: undefined,
126 |
127 | // A list of paths to directories that Jest should use to search for files in
128 | // roots: [
129 | // ""
130 | // ],
131 |
132 | // Allows you to use a custom runner instead of Jest's default test runner
133 | // runner: "jest-runner",
134 |
135 | // The paths to modules that run some code to configure or set up the testing environment before each test
136 | // setupFiles: [],
137 |
138 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
139 | // setupFilesAfterEnv: [],
140 |
141 | // The number of seconds after which a test is considered as slow and reported as such in the results.
142 | // slowTestThreshold: 5,
143 |
144 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
145 | // snapshotSerializers: [],
146 |
147 | // The test environment that will be used for testing
148 | testEnvironment: 'jest-environment-node',
149 |
150 | // Options that will be passed to the testEnvironment
151 | // testEnvironmentOptions: {},
152 |
153 | // Adds a location field to test results
154 | // testLocationInResults: false,
155 |
156 | // The glob patterns Jest uses to detect test files
157 | // testMatch: [
158 | // "**/__tests__/**/*.[jt]s?(x)",
159 | // "**/?(*.)+(spec|test).[tj]s?(x)"
160 | // ],
161 |
162 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
163 | // testPathIgnorePatterns: [
164 | // "/node_modules/"
165 | // ],
166 |
167 | // The regexp pattern or array of patterns that Jest uses to detect test files
168 | // testRegex: [],
169 |
170 | // This option allows the use of a custom results processor
171 | // testResultsProcessor: undefined,
172 |
173 | // This option allows use of a custom test runner
174 | // testRunner: "jest-circus/runner",
175 |
176 | // A map from regular expressions to paths to transformers
177 | // transform: undefined,
178 | transform: {
179 | "^.+\\.(t|j)sx?$": "ts-jest",
180 | ".+\\.(css|scss|png|jpg|svg)$": "jest-transform-stub"
181 | },
182 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
183 | // transformIgnorePatterns: [
184 | // "/node_modules/",
185 | // "\\.pnp\\.[^\\/]+$"
186 | // ],
187 |
188 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
189 | // unmockedModulePathPatterns: undefined,
190 |
191 | // Indicates whether each individual test should be reported during the run
192 | // verbose: undefined,
193 |
194 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
195 | // watchPathIgnorePatterns: [],
196 |
197 | // Whether to use watchman for file crawling
198 | // watchman: true,
199 | };
200 |
201 | module.exports = config;
202 |
--------------------------------------------------------------------------------
/flake-guard-app/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import 'ts-node/register';
2 |
--------------------------------------------------------------------------------
/flake-guard-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flake-guard",
3 | "version": "1.0.0",
4 | "description": "Stability Starts Here: Master Test Flakiness, Ensure Reliability",
5 | "main": "./src/client/index.tsx",
6 | "scripts": {
7 | "start": "concurrently \"webpack serve --open\" \"nodemon ./src/server/server.ts\"",
8 | "test": "jest",
9 | "build": "webpack --mode=production --node-env=production",
10 | "build:dev": "webpack --mode=development",
11 | "build:prod": "webpack --mode=production --node-env=production",
12 | "watch": "webpack --watch",
13 | "serve": "webpack serve",
14 | "lint": "gts lint",
15 | "clean": "gts clean",
16 | "compile": "tsc",
17 | "fix": "gts fix"
18 | },
19 | "author": "",
20 | "license": "ISC",
21 | "devDependencies": {
22 | "@babel/core": "^7.24.8",
23 | "@babel/preset-env": "^7.24.8",
24 | "@babel/preset-react": "^7.24.7",
25 | "@babel/preset-typescript": "^7.24.7",
26 | "@eslint/js": "^9.3.0",
27 | "@jest/globals": "^29.7.0",
28 | "@testing-library/dom": "^10.3.2",
29 | "@testing-library/jest-dom": "^6.4.6",
30 | "@testing-library/react": "^16.0.0",
31 | "@types/bootstrap": "^5.2.10",
32 | "@types/cors": "^2.8.17",
33 | "@types/express": "^4.17.21",
34 | "@types/faker": "^6.6.9",
35 | "@types/jest": "^29.5.12",
36 | "@types/lodash": "^4.17.6",
37 | "@types/node": "^20.14.2",
38 | "@types/react": "^18.3.3",
39 | "@types/react-dom": "^18.3.0",
40 | "@types/react-redux": "^7.1.33",
41 | "@types/react-router-dom": "^5.3.3",
42 | "@types/webpack": "^5.28.5",
43 | "babel-jest": "^29.7.0",
44 | "css-loader": "^7.1.2",
45 | "eslint": "^8.57.0",
46 | "eslint-plugin-react": "^7.34.4",
47 | "globals": "^15.3.0",
48 | "gts": "^5.3.0",
49 | "html-webpack-plugin": "^5.6.0",
50 | "jest": "^29.7.0",
51 | "jest-environment-jsdom": "^29.7.0",
52 | "mini-css-extract-plugin": "^2.9.0",
53 | "style-loader": "^4.0.0",
54 | "tailwindcss": "^3.4.4",
55 | "ts-jest": "^29.2.2",
56 | "ts-loader": "^9.5.1",
57 | "ts-node": "^10.9.2",
58 | "tslint-config-prettier": "^1.18.0",
59 | "typescript": "^5.4.5",
60 | "typescript-eslint": "^7.11.0",
61 | "webpack": "^5.91.0",
62 | "webpack-cli": "^5.1.4",
63 | "webpack-dev-server": "^5.0.4",
64 | "workbox-webpack-plugin": "^7.1.0"
65 | },
66 | "dependencies": {
67 | "@coreui/icons": "^3.0.1",
68 | "@coreui/react": "^5.1.0",
69 | "@emotion/css": "^11.11.2",
70 | "@emotion/react": "^11.11.4",
71 | "@emotion/styled": "^11.11.5",
72 | "@fontsource/roboto": "^5.0.13",
73 | "@mui/icons-material": "^5.16.4",
74 | "@mui/material": "^5.16.4",
75 | "@mui/styled-engine-sc": "^6.0.0-alpha.18",
76 | "@mui/system": "^5.15.20",
77 | "@mui/x-charts": "^7.7.0",
78 | "@nivo/bar": "^0.87.0",
79 | "@nivo/calendar": "^0.87.0",
80 | "@nivo/core": "^0.87.0",
81 | "@nivo/line": "^0.87.0",
82 | "@nivo/pie": "^0.87.0",
83 | "@reduxjs/toolkit": "^2.2.5",
84 | "@supabase/supabase-js": "^2.43.4",
85 | "@testing-library/user-event": "^14.5.2",
86 | "@types/chart.js": "^2.9.41",
87 | "axios": "^1.7.4",
88 | "body-parser": "^1.20.3",
89 | "bootstrap": "^5.3.3",
90 | "chart.js": "^4.4.3",
91 | "chartjs-plugin-datalabels": "^2.2.0",
92 | "concurrently": "^8.2.2",
93 | "cors": "^2.8.5",
94 | "dotenv": "^16.4.5",
95 | "express": "^4.21.0",
96 | "faker": "^5.5.3",
97 | "jest-transform-css": "^6.0.1",
98 | "jest-transform-stub": "^2.0.0",
99 | "nodemon": "^3.1.2",
100 | "postgres": "^3.4.4",
101 | "react": "^17.0.0 || ^18.0.0",
102 | "react-bootstrap": "^2.10.4",
103 | "react-chartjs-2": "^5.2.0",
104 | "react-dom": "^17.0.0 || ^18.0.0",
105 | "react-icons": "^5.2.1",
106 | "react-icons-kit": "^2.0.0",
107 | "react-pro-sidebar": "^1.1.0",
108 | "react-redux": "^9.1.2",
109 | "react-router-dom": "^6.25.0",
110 | "recharts": "^2.12.7",
111 | "styled-components": "^6.1.11"
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/app.test.tsx:
--------------------------------------------------------------------------------
1 |
2 | /** @jest-environment jsdom */
3 | //https://testing-library.com/docs/example-react-router/
4 | import * as React from 'react';
5 | import { render } from '@testing-library/react';
6 | import App from './app';
7 |
8 | describe('App', () => {
9 | it('renders App component', () => {
10 | render( );
11 |
12 |
13 | });
14 | });
15 |
16 | //NEED TO MANUALLY SET THE ENVIRONMENT BECAUSE OUR CONFIG ENVIRONMENT IS SET WITH NODE ENVIRONMENT FOR BACKEND TESTING
--------------------------------------------------------------------------------
/flake-guard-app/src/client/app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Landing from './landingPage/LandingPage';
3 | import DocPage from './docs/DocPage';
4 | import UserDashboard from './userDash/UserDashboard';
5 | import TempDashboard from './tempDash/TempDashboard';
6 | import DecisionPage from './tempDash/DecisionPage';
7 | import History from './userDash/pages/history/History';
8 | import FlakyTests from './userDash/pages/flakyTests/FlakyTests'
9 | import ResultsProvider from './userDash/contexts/ResultContext';
10 | import CodeCoverage from './userDash/pages/codeCoverage/CodeCoverage';
11 |
12 | import {
13 | BrowserRouter as Router,
14 | Routes,
15 | Route,
16 | Navigate,
17 | } from 'react-router-dom';
18 |
19 | const App: React.FC = () => {
20 | // Define the style object
21 |
22 | return (
23 |
24 | {/* Alias of BrowserRouter i.e. Router */}
25 |
26 |
27 | } />
28 | } />
29 | } />
30 | } />
31 | {/* } /> */}
32 |
36 |
37 |
38 | }
39 | />
40 |
44 |
45 |
46 | }
47 | />
48 |
52 |
53 |
54 | }
55 | />
56 |
58 |
59 |
60 | } />
61 | {/* } /> */}
62 | {/* Redirect any unmatched routes to home */}
63 | } />
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default App;
71 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/advantage1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/advantage1.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/advantage2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/advantage2.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/advantage3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/advantage3.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/advantage4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/advantage4.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/arrow.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/ashley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/ashley.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/brendan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/brendan.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/condensed-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/condensed-logo.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/download.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/flakeguard-logo-white-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/flakeguard-logo-white-background.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/github-logo.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/github-mark-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/github-mark-white.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/github-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/github-mark.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/graphs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/graphs.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/linkedinlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/linkedinlogo.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/logo.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/logo_with_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/logo_with_background.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/npm.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/paloma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/paloma.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/signout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/signout.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/tommy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/tommy.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/assets/will.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/flake-guard-alpha/421e573779f1d5d3fe663263dd9b0f181840b053/flake-guard-app/src/client/assets/will.png
--------------------------------------------------------------------------------
/flake-guard-app/src/client/docs/DocPage.tsx:
--------------------------------------------------------------------------------
1 | import Header from '../landingPage/components/NavBar';
2 | import Footer from '../landingPage/components/Footer';
3 | import '../styles/styles.css';
4 | import '../styles/docs.css';
5 | import FAQ from './FAQ';
6 |
7 | const DocPage = (): JSX.Element => {
8 | return (
9 | <>
10 |
11 |
12 |
13 |
19 |
29 | What is FlakeGuard?
30 |
31 |
41 | Installation
42 |
43 |
53 | FAQ
54 |
55 |
56 |
57 |
63 |
What is FlakeGuard?
64 |
65 | FlakeGuard a free, open-source, tool that allows developers to
66 | run Jest tests to automatically detect, report, and manage flaky
67 | tests in software development.
68 |
69 |
Personalized Features
70 |
71 | Things you can do with your personal Flake Guard dashboard:
72 |
73 | View analytics including, but not limited to:
74 |
75 | Passing vs failing tests with the color coordinated flake severity table
76 | Analyze Runtime metrics & API response times
77 | See flags and recommendations to fix flaky tests
78 | View pointers showing fail points within test code
79 | Running test order permutation
80 | {/* Flagged tests using ‘async’ calls */}
81 |
82 | Display flake improvement over time
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
Installation
97 |
98 | Terminal Command:
99 |
100 | npm i flake-guard
101 |
102 | Installation as dev dependency:{' '}
103 |
104 | npm i flake-guard --save-dev
105 |
106 | Run Flake Guard:{' '}
107 | npx flake-guard
Or
108 |
npx flake-guard {''}
Please change {`
`} with the appropriate name of the
109 | test file that you want to examine. Flake Guard will analyze your
110 | E2E tests for flakiness by executing multiple test runs and
111 | analyzing the results.
112 |
113 | Configuration
114 |
115 | The default number of test runs is 10,
116 | but this can be adjusted as described below.
117 | In general, there is a time versus accuracy tradeoff. More test
118 | executions increase accuracy but reduce speed.
119 | To adjust FlakeGuard configuration variables, you can create a
120 | file in the root directory of your project called{' '}
121 | fg.config.json
Below are the defaults, which can
122 | be overridden in your local 'fg.config.json' file.
123 |
124 |
125 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | >
138 | );
139 | };
140 |
141 | export default DocPage;
142 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/docs/FAQ.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/docs.css';
2 |
3 | const Faq = (): JSX.Element => {
4 | return (
5 |
6 |
7 |
8 |
9 |
17 | What is a flaky test?
18 |
19 |
20 |
26 |
27 | A flaky test is a test that sometimes passes and sometimes fails
28 | for the same code, often due to nondeterministic factors like
29 | timing issues, network variability, or reliance on external
30 | systems.
31 |
32 |
33 |
34 |
35 |
36 |
44 | What causes flaky tests?
45 |
46 |
47 |
53 |
54 | Flaky tests can be caused by race conditions, timing issues,
55 | network instability, environmental dependencies, or inconsistent
56 | external service responses.
57 |
58 |
59 |
60 |
61 |
62 |
70 | When should I use FlakeGuard?
71 |
72 |
73 |
79 |
You should use a FlakeGuard when you notice intermittent test
80 | failures, to identify and address unstable tests, ensuring more
81 | reliable and consistent test results.
82 |
83 |
84 |
85 |
86 |
87 |
95 | What's the impact of flaky tests?
96 |
97 |
98 |
104 |
105 | Reduced Confidence:
106 | They undermine trust in the test suite, as developers can't rely
107 | on test results to reflect the true state of the code.
108 |
109 |
110 | Wasted Time:
111 | Developers spend time diagnosing and rerunning tests instead of
112 | focusing on new features or bug fixes.
113 |
114 |
115 | Masked Issues:
116 | Real bugs might be overlooked or ignored, assuming test failures
117 | are just flakiness.
118 |
119 |
120 | Increased Maintenance:
121 | More effort is needed to maintain and stabilize the test suite,
122 | which can slow down development cycles.
123 |
124 |
125 | Continuous Integration Disruptions:
126 | They can cause interruptions in automated build and deployment
127 | processes, delaying releases.
128 |
129 |
130 |
131 |
132 |
133 |
134 |
142 | What are the best practices to prevent flaky tests?
143 |
144 |
145 |
151 |
To prevent flaky tests, ensure tests are isolated to avoid shared state dependencies. Control timing with fixed timeouts and avoid relying on fluctuating conditions. Mock external dependencies for consistent interactions and reduce variability. Use stable, controlled test data to prevent unexpected input changes. Run tests in a repeatable environment for consistency and reproducibility, boosting confidence in results and accelerating
152 | development cycles.
153 |
154 |
155 |
156 | {/*
157 |
158 |
166 | How well does FlakeGuard “scale” in terms of performance?
167 |
168 |
169 |
*/}
178 | {/*
*/}
179 |
180 |
181 | );
182 | };
183 |
184 | export default Faq;
185 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/docs/faq.test.tsx:
--------------------------------------------------------------------------------
1 | /** @jest-environment jsdom */
2 |
3 | import * as React from 'react';
4 | import { render, screen } from '@testing-library/react';
5 | import userEvent from '@testing-library/user-event';
6 | import '@testing-library/jest-dom'; // Import the matchers
7 | import Faq from './FAQ';
8 |
9 | describe('FAQ', () => {
10 | test('renders FAQ questions', () => {
11 | render( );
12 |
13 | // Assert that the FAQ section is open
14 | // const firstFaq = screen.getByText('What is a flaky test?');
15 | // expect(firstFaq).toBeTruthy();
16 |
17 | // const firstAnswer = screen.getByText('A flaky test is a test that sometimes passes and sometimes fails for the same code, often due to nondeterministic factors like timing issues, network variability, or reliance on external systems..')
18 | // expect(firstAnswer).toBeTruthy();
19 |
20 | // const secondFaq = screen.getByText('What causes flaky tests?');
21 | // expect(secondFaq).toBeTruthy();
22 |
23 | // const thirdFaq = screen.getByText('When should I use FlakeGuard?');
24 | // expect(thirdFaq).toBeTruthy();
25 |
26 | // const fourthFaq = screen.getByText(`What's the impact of flaky tests?`);
27 | // expect(fourthFaq).toBeTruthy();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/Analytics/assertion-failures-percent.tsx:
--------------------------------------------------------------------------------
1 | import {TestResult} from '../../types';
2 |
3 | export const assertionFailedPercent = (
4 | results: TestResult[]
5 | ): string[] | undefined => {
6 | if (!results || results.length === 0) return undefined;
7 |
8 | const failingAssertions = results
9 | .filter(result => result.failed > 0 && result.passed === 0)
10 | .map(result => result.name);
11 |
12 | return failingAssertions.length > 0 ? failingAssertions : undefined;
13 | };
14 | // src/client/Analytics/assertion-failures-percent.ts
15 | // import {TestResult} from '../../types';
16 |
17 | // export const assertionFailedPercent = (results: TestResult[]): string[] => {
18 | // return results
19 | // .filter(result => result.passed === 0 && result.failed > 0)
20 | // .map(result => result.name);
21 | // };
22 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/Analytics/flake-percentage.tsx:
--------------------------------------------------------------------------------
1 | // flakePercentageUtils.ts
2 |
3 | interface AssertionResult {
4 | passed: boolean;
5 | failed: boolean;
6 | }
7 |
8 | // export const calculateFlakePercentage = (
9 | // results: TestResult[]
10 | // ): number | undefined => {
11 | // let totalPassed = 0;
12 | // let totalFailed = 0;
13 | // let totalSkipped = 0;
14 |
15 | // for (const result of results) {
16 | // totalPassed += result.passed;
17 | // totalFailed += result.failed;
18 | // totalSkipped += result.skipped;
19 | // }
20 |
21 | // const totalTests = totalPassed + totalFailed + totalSkipped;
22 | // if (totalTests === 0) {
23 | // return undefined; // Return undefined if no tests were executed
24 | // }
25 |
26 | // const flakePercentage = (totalFailed / totalTests) * 100;
27 | // return flakePercentage;
28 | // };
29 | export const calculateFlakePercentage = (
30 | assertionResults: AssertionResult[]
31 | ): number | undefined => {
32 | let flakyCount = 0;
33 | let totalAssertions = 0;
34 |
35 | for (const assertion of assertionResults) {
36 | totalAssertions++;
37 |
38 | // Check if the assertion has at least one pass and one fail
39 | if (assertion.passed && assertion.failed) {
40 | flakyCount++;
41 | }
42 | }
43 |
44 | if (totalAssertions === 0) {
45 | return undefined; // No assertions were executed
46 | }
47 |
48 | const flakePercentage = (flakyCount / totalAssertions) * 100;
49 | return flakePercentage;
50 | };
51 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/Analytics/overall-failed-percentage.tsx:
--------------------------------------------------------------------------------
1 | // TotalfailedPercentageUtils.ts
2 |
3 | interface TestResult {
4 | passed: number;
5 | failed: number;
6 | skipped: number;
7 | }
8 |
9 | export const failedPercentage = (results: TestResult[]): number | undefined => {
10 | let totalPassed = 0;
11 | let totalFailed = 0;
12 | let totalSkipped = 0;
13 |
14 | for (const result of results) {
15 | totalPassed += result.passed;
16 | totalFailed += result.failed;
17 | totalSkipped += result.skipped;
18 | }
19 |
20 | const totalTests = totalPassed + totalFailed + totalSkipped;
21 | if (totalTests === 0) {
22 | return undefined; // Return undefined if no tests were executed
23 | }
24 |
25 | const failedPercentage = (totalFailed / totalTests) * 100;
26 | return failedPercentage;
27 | };
28 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/FlakeRiskSign/FlakeRiskArrow.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | interface ArrowPositions {
4 | low: number;
5 | moderate: number;
6 | high: number;
7 | veryHigh: number;
8 | extreme: number;
9 | }
10 |
11 | interface FlakeRiskArrowProps {
12 | flakePercent: number | undefined;
13 | }
14 |
15 | const FlakeRiskArrow: React.FC = ({ flakePercent }): JSX.Element => {
16 | const positions: ArrowPositions = {
17 | low: -72,
18 | moderate: -36,
19 | high: 0,
20 | veryHigh: 36,
21 | extreme: 72,
22 | };
23 |
24 | const getArrowStyle = () => {
25 | if (flakePercent === undefined) return {};
26 |
27 | if (flakePercent === 0) return { transform: `rotate(${positions.low}deg)` };
28 | if (flakePercent > 0 && flakePercent < 10) return { transform: `rotate(${positions.moderate}deg)` };
29 | if (flakePercent >= 10 && flakePercent < 20) return { transform: `rotate(${positions.high}deg)` };
30 | if (flakePercent >= 20 && flakePercent <= 30) return { transform: `rotate(${positions.veryHigh}deg)` };
31 | return { transform: `rotate(${positions.extreme}deg)` };
32 | };
33 |
34 | return (
35 |
36 | );
37 | };
38 |
39 | export default FlakeRiskArrow;
40 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/FlakeRiskSign/FlakeRiskContainer.tsx:
--------------------------------------------------------------------------------
1 | import FlakeRiskSign from './FlakeRiskSign';
2 | import '../../styles/riskSign.css';
3 |
4 | interface FlakeRiskContainerProps {
5 | flakePercent: number | undefined
6 | }
7 |
8 | const FlakeRiskContainer: React.FC = ({flakePercent}): JSX.Element => {
9 | return (
10 |
11 |
FLAKE DANGER TODAY
12 |
13 |
14 | ONLY YOU CAN PREVENT FLAKY TESTS
15 |
16 |
17 | );
18 | };
19 |
20 | export default FlakeRiskContainer;
21 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/FlakeRiskSign/FlakeRiskSign.tsx:
--------------------------------------------------------------------------------
1 | import FlakeRiskSlice from './FlakeRiskSlice';
2 | import FlakeRiskArrow from './FlakeRiskArrow';
3 |
4 | interface FlakeRiskSignProps {
5 | flakePercent: number | undefined
6 | }
7 |
8 | const FlakeRiskSign: React.FC = ({flakePercent}): JSX.Element => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default FlakeRiskSign;
22 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/flakeRiskSign/FlakeRiskSign/FlakeRiskSlice.tsx:
--------------------------------------------------------------------------------
1 | interface SliceProps {
2 | color: string;
3 | text: string;
4 | }
5 |
6 | const FlakeRiskSlice = (props: SliceProps): JSX.Element => {
7 | return (
8 |
9 |
{props.text}
10 |
11 | );
12 | };
13 |
14 | export default FlakeRiskSlice;
15 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FlakeGuard
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/index.tsx:
--------------------------------------------------------------------------------
1 | import {createRoot} from 'react-dom/client';
2 | import App from './app';
3 |
4 | const container = document.getElementById('react-root')!;
5 | //! is telling TS, saying that container will never be 'null'
6 | const root = createRoot(container);
7 |
8 | //Hello, I removed for now because it causes React to load twice -Paloma
9 | root.render(
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/LandingPage.tsx:
--------------------------------------------------------------------------------
1 | import Header from './components/NavBar';
2 | import Advantages from './components/Advantages';
3 | import download from '../assets/download.png';
4 | import Footer from './components/Footer';
5 | import {Link} from 'react-router-dom';
6 | import graphs from '../assets/graphs.png'
7 | import '../styles/landingPage.css';
8 | import MeetTheTeam from './components/MeetTheTeam';
9 |
10 |
11 | const Landing = (): JSX.Element => {
12 | return (
13 | <>
14 |
15 |
16 |
17 |
Save Thousands of Debugging Hours
18 |
19 |
20 | Flake Guard is a free, open-source, software that allows developers to automatically run Jest testing files to detect, report, and manage flaky tests in development.
21 |
22 |
23 | By identifying and handling these inconsistent tests, Flake Guard
24 | helps maintain test reliability.
25 |
26 |
27 |
28 | Install npm package
29 |
34 |
35 |
36 |
37 | To learn more about FlakeGuard, click{' '}
38 |
42 | here
43 |
44 | .
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
PLACEHOLDER FOR INSTRUCTIONS OF HOW TO INSTALL FG
54 |
55 |
56 |
57 |
58 |
59 |
60 | >
61 | );
62 | };
63 |
64 | export default Landing;
65 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/Advantages.tsx:
--------------------------------------------------------------------------------
1 | import '../../styles/advantages.css';
2 | import advantage1 from '../../assets/advantage1.png';
3 | import advantage2 from '../../assets/advantage2.png';
4 | import advantage3 from '../../assets/advantage3.png';
5 | import advantage4 from '../../assets/advantage4.png';
6 |
7 | const Advantages = (): JSX.Element => {
8 | return (
9 |
10 |
11 |
12 |
Improved Test Reliability
13 |
14 | By identifying flaky tests, Flake Guard helps developers resolve
15 | inconsistencies, leading to more reliable and trustworthy test suites.
16 |
17 |
18 |
19 |
20 |
Enhanced Developer Productivity
21 |
22 | Detecting and fixing flaky tests saves troubleshooting time, letting
23 | developers focus on writing quality code.
24 |
25 |
26 |
27 |
28 |
Increased Confidence in Software Releases
29 |
30 | With fewer flaky tests, teams can be more confident in the results of
31 | their test suites, ensuring that only stable and well-tested code is
32 | released to production.
33 |
34 |
35 |
36 |
37 |
Faster Continuous Integration (CI) Pipelines
38 |
39 | Reducing flaky tests minimizes false negatives in CI pipelines,
40 | resulting in smoother, faster processes, and accelerating development
41 | cycles.
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Advantages;
49 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import {Link} from 'react-router-dom';
2 | import github from '../../assets/github-mark-white.png';
3 | import npm from '../../assets/npm.png';
4 |
5 | import '../../styles/footer.css';
6 |
7 | const Footer = (): JSX.Element => {
8 | return (
9 |
33 | );
34 | };
35 |
36 | export default Footer;
37 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/LoginButton.tsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect} from 'react';
2 | import {Link} from 'react-router-dom';
3 | import {supabaseClient} from '../../supabaseClient';
4 | import signout from '../../assets/signout.png';
5 | import github from '../../assets/github-mark-white.png';
6 |
7 | interface User {
8 | email: string;
9 | profile: string;
10 | }
11 |
12 | const LoginButton: React.FC = () => {
13 | const [user, setUser] = useState(null);
14 | const [authChecked, setAuthChecked] = useState(false);
15 |
16 | useEffect(() => {
17 | checkUser();
18 | window.addEventListener('hashchange', () => {
19 | checkUser();
20 | });
21 | }, []);
22 |
23 | async function checkUser() {
24 | try {
25 | const {data, error} = await supabaseClient.auth.getUser();
26 | if (!error && data.user) {
27 | console.log('USER DATA --->', data.user);
28 |
29 | setUser({
30 | email: data.user.user_metadata.user_name,
31 | profile: data.user.user_metadata.avatar_url,
32 | });
33 | } else {
34 | setUser(null);
35 | }
36 | setAuthChecked(true);
37 | } catch (error) {
38 | console.error('Error fetching user:', error);
39 | setUser(null);
40 | }
41 | }
42 |
43 | async function signInWithGithub() {
44 | try {
45 | await supabaseClient.auth.signInWithOAuth({
46 | provider: 'github',
47 | });
48 | } catch (error) {
49 | console.error('Error signing in: ', error);
50 | }
51 | }
52 |
53 | async function signOut() {
54 | await supabaseClient.auth.signOut();
55 | setUser(null);
56 | }
57 |
58 | if (user && authChecked) {
59 | return (
60 |
61 |
66 |
71 |
72 |
73 |
74 | {user.email}
75 |
76 |
77 |
83 |
84 | Sign out
85 |
86 |
87 |
88 |
89 | );
90 | } else if (authChecked) {
91 | return (
92 |
93 |
94 |
95 | Sign in with GitHub
96 |
97 |
98 | );
99 | } else return <>>;
100 | };
101 |
102 | export default LoginButton;
103 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/MeetTheTeam.tsx:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import React from 'react';
3 | import '../../styles/landingPage.css';
4 | import Avatar from '@mui/material/Avatar';
5 | import ashley from '../../assets/ashley.png';
6 | import paloma from '../../assets/paloma.png';
7 | import will from '../../assets/will.png';
8 | import brendan from '../../assets/brendan.png';
9 | import tommy from '../../assets/tommy.png';
10 |
11 | import linkedInLogo from '../../assets/linkedinlogo.png';
12 | import githubLogo from '../../assets/github-logo.png'
13 | //NEEDING TOMMY INFO -> LINKEDIN AND IMAGE
14 | //STRETCH FEATURES -> WHEN CLICKING OUR TEAM -> GET DIRECTED TO THIS SPOT ON THE PAGE]
15 |
16 | const TeamMember = () => {
17 | const name= ["Ashley", "Paloma", "Tommy", "Brendan", "Will"];
18 | const role="Full Stack Developer"
19 | return (
20 | <>
21 |
22 |
23 |
{name[0]}
24 |
{role}
25 |
33 |
34 |
35 |
36 |
{name[1]}
37 |
{role}
38 |
46 |
47 |
48 |
49 |
{name[2]}
50 |
{role}
51 |
59 |
60 |
61 |
62 |
{name[3]}
63 |
{role}
64 |
72 |
73 |
74 |
75 |
{name[4]}
76 |
{role}
77 |
85 |
86 | >
87 | );
88 | };
89 |
90 | const Team = () => {
91 | const teamMembers = [
92 | {
93 | name: 'John Doe',
94 | role: 'CEO',
95 | image: 'path/to/john-doe.jpg',
96 | socialLinks: {
97 | linkedin: 'https://www.linkedin.com/in/johndoe',
98 | twitter: 'https://twitter.com/johndoe'
99 | }
100 | },
101 | // Add more team members here
102 | ];
103 |
104 | return (
105 |
106 | Meet Our Team
107 |
108 | {teamMembers.map((name, index) => (
109 |
110 | ))}
111 |
112 |
113 | );
114 | };
115 |
116 | export default Team;
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/NavBar.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import {supabaseClient} from '../../supabaseClient';
3 | import {Link} from 'react-router-dom';
4 | import logo from '../../assets/logo.png';
5 | import LoginButton from './LoginButton';
6 | import '../../styles/header.css';
7 |
8 | const NavBarHeading: React.FC = () => {
9 | const [userId, setUserId] = useState(null);
10 |
11 | useEffect(() => {
12 | const isLoggedIn = async () => {
13 | try {
14 | const {data, error} = await supabaseClient.auth.getUser();
15 | if (data && !error) {
16 | setUserId(data.user.id);
17 | }
18 | } catch (error) {
19 | console.error('Error checking user auth: ', error);
20 | }
21 | };
22 | isLoggedIn();
23 | }, []);
24 |
25 | return (
26 |
27 |
28 |
29 |
35 |
36 |
Docs
37 |
Blog
38 | {userId &&
Dashboard}
39 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default NavBarHeading;
48 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/landingPage/components/advantages.test.tsx:
--------------------------------------------------------------------------------
1 | /** @jest-environment jsdom */
2 |
3 | import React from 'react';
4 | import { render, screen } from '@testing-library/react';
5 | import '@testing-library/jest-dom';
6 | import Advantages from './Advantages';
7 |
8 | describe('Advantages Component', () => {
9 | it('should display "Improved Test Reliability" text', () => {
10 | // Render the Advantages component
11 | render( );
12 |
13 | // Check if the text "Improved Test Reliability" is in the document
14 | const improvedReliability = screen.getByText('Improved Test Reliability');
15 | expect(improvedReliability).toBeInTheDocument();
16 | });
17 |
18 | it('should display all advantages texts', () => {
19 | // Render the Advantages component
20 | render( );
21 |
22 | expect(screen.getByText('Improved Test Reliability')).toBeInTheDocument();
23 | expect(screen.getByText('Enhanced Developer Productivity')).toBeInTheDocument();
24 | expect(screen.getByText('Increased Confidence in Software Releases')).toBeInTheDocument();
25 | expect(screen.getByText('Faster Continuous Integration (CI) Pipelines')).toBeInTheDocument();
26 | expect(screen.getByTestId('paragraph1')).toBeInTheDocument();
27 | expect(screen.getByTestId('paragraph2')).toBeInTheDocument();
28 | expect(screen.getByTestId('paragraph3')).toBeInTheDocument();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/services/index.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const api = axios.create({
4 | baseURL: 'https://flakeguard.com/',
5 | });
6 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/advantages.css:
--------------------------------------------------------------------------------
1 | .advantages-container {
2 | display: flex;
3 | justify-content: center;
4 | width: 100%;
5 | height: 300px;
6 | margin: auto;
7 | font-size: 15px;
8 | border-top: 1px solid #e6e7e8;
9 |
10 | }
11 |
12 | .advantage {
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | flex-direction: column;
17 | }
18 |
19 | .advantage h5 {
20 | text-align: center;
21 | width: 250px;
22 | color: #376b32;
23 | margin-top: 15px;
24 | }
25 | .advantage p {
26 | text-align: justify;
27 | width: 250px;
28 | margin-left: 20px;
29 | padding: 10px;
30 | color: rgb(60, 60, 60);
31 | }
32 |
33 | @media screen and (max-width: 1064px) {
34 | .advantages-container {
35 | display: flex;
36 | flex-wrap: wrap;
37 | height: 100%;
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/dashboard.css:
--------------------------------------------------------------------------------
1 | /* SUMMARY */
2 | .dashboard-container {
3 | color: #474646;
4 | }
5 | .dashboard-container h1{
6 | text-align: center;
7 | margin: 10px;
8 | }
9 | .upper-dash {
10 | display: flex;
11 | justify-content: space-evenly;
12 | width: 80%;
13 | margin: auto;
14 |
15 | }
16 | .box-style {
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | background-color: white;
21 | height: 400px;
22 | border-radius: 10px;
23 | box-shadow: 0px 0px 20px grey;
24 | margin: 10px;
25 | }
26 | .summary-dash {
27 | width: 300px;
28 | }
29 |
30 | /* ASSERTION RESULTS */
31 | /* .assertions-graph {
32 | width: 100%;
33 | height: 100%;
34 | padding: 5px;
35 | } */
36 |
37 | /* DISPLAY ERRORS */
38 | .display-errors-container {
39 | width: 80%;
40 | margin: auto;
41 | }
42 | .display-errors-container p {
43 | border-bottom: 1px solid #d0d3d3;
44 | font-weight: 500;
45 | }
46 | .errors-dash {
47 | background-color: white;
48 | width: 100%;
49 | padding: 10px;
50 | border-radius: 10px;
51 | box-shadow: 0px 0px 20px grey;
52 | margin: 10px;
53 | }
54 |
55 | .errors-dash button {
56 | background: none;
57 | color: inherit;
58 | border: none;
59 | padding: 0;
60 | font: inherit;
61 | cursor: pointer;
62 | outline: inherit;
63 |
64 | }
65 |
66 | .errors-details p {
67 | color: #5b5b5b;
68 | border: none;
69 | margin-left: 20px;
70 |
71 | }
72 |
73 |
74 | /* FLAKINESS */
75 | .flakiness {
76 | height: 100%;
77 | }
78 | /* .flakiness-container {
79 | height: 100%;
80 | width: 100%;
81 | }
82 | .flakiness-box {
83 | display: flex;
84 | justify-content: center;
85 | align-items: center;
86 | flex-direction: column;
87 | width: 100%;
88 | } */
89 | .progress-bar {
90 | display: flex;
91 | justify-content: center;
92 | width: 150px;
93 | height: 28px;
94 | }
95 |
96 | .progress-bar-label {
97 | text-align: center;
98 | height: 28px;
99 | color: white;
100 |
101 | }
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/dashboard/charts.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | /* PIE CHART */
4 | .piechart-container {
5 | width: 30%;
6 | height: 350px;
7 | }
8 |
9 | .flaky-count {
10 | background-color: rgb(254, 223, 99, 0.8) ;
11 | width: 110px;
12 | border-radius: 20px;
13 | margin: auto;
14 | }
15 | .flaky-count p {
16 | font-size: 13px;
17 | color: #8a5d2a;
18 | text-align: center;
19 | padding: 2px;
20 | }
21 |
22 | /* BARCHART */
23 | .barchart-container {
24 | height: 350px;
25 | width: 50%;
26 | }
27 | /* BOTTOM CONTENT */
28 | .accordion-scrollable {
29 | max-height: 300px;
30 | overflow-y: auto;
31 | width: 100%;
32 | }
33 |
34 | /* ERRORS */
35 | .errors-details-container {
36 | width: 50%;
37 | }
38 |
39 | .errors-details-graph {
40 | height: 250px;
41 | width: 100%;
42 | }
43 |
44 | .bottom-right-three-boxes {
45 | display: flex;
46 | justify-content: space-evenly;
47 |
48 | }
49 | /* DURATION */
50 | .duration-title {
51 | display: flex;
52 | align-items: center;
53 | margin: auto;
54 | width: 100%;
55 | font-size: 20px;
56 | margin-left: 5px;
57 | }
58 |
59 | .duration-container {
60 | display: flex;
61 | justify-content: center;
62 | align-items: center;
63 | }
64 |
65 | /* CALENDAR */
66 | .calendar-title {
67 | text-align: center;
68 | width: 100%;
69 | font-size: 20px;
70 | padding-top: 5px;
71 | }
72 |
73 |
74 | @media screen and (max-width: 1350px) {
75 | .flakiness-details {
76 | background-color: red;
77 | display: none;
78 | }
79 | .piechart-container,
80 | .barchart-container {
81 | width: 50%;
82 | }
83 |
84 | }
85 |
86 | @media screen and (max-width: 768px) {
87 | .calendar-container,
88 | .flakiness-details,
89 | .barchart-container,
90 | .duration-container,
91 | .sidebar-container {
92 | display: none;
93 | }
94 | .bottom-content {
95 | margin-left: 20px;
96 | width: 750px;
97 | }
98 |
99 | .piechart-container,
100 | .linechart-container,
101 | .errors-details-container,
102 | .errors-details-graph {
103 | width: 450px;
104 | }
105 | }
106 |
107 |
108 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/dashboard/newDashboard.css:
--------------------------------------------------------------------------------
1 | .dashboard-container {
2 | display: flex;
3 | }
4 | .dashboard-content {
5 | width: 100%;
6 | }
7 | .dashboard-title {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: end;
11 | margin: 10px;
12 | font-weight: 600;
13 | }
14 |
15 | .top-content {
16 | display: flex;
17 | justify-content: space-evenly;
18 | width: 100%;
19 | height: 350px;
20 | }
21 | .flakiness-details {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | flex-direction: column;
26 | width: 17%;
27 | height: 360px;
28 |
29 | }
30 |
31 | .flakiness-box {
32 | padding: 5px;
33 | height: 100%;
34 | width: 100%;
35 | }
36 | .flakiness-box-title {
37 | display: flex;
38 | align-items: center;
39 | width: 100%;
40 | font-size: 20px;
41 | margin-left: 5px;
42 | }
43 |
44 | .info-icon {
45 | margin-left: 5px;
46 | }
47 |
48 | .flaky-metrics {
49 | width: 90%;
50 | }
51 |
52 | .flakiness-text{
53 | color: rgb(185, 185, 185);
54 | font-weight: 500;
55 | margin-left: 5px;
56 | }
57 |
58 | /* MIDDLE CONTENT */
59 | .middle-content {
60 | display: flex;
61 | justify-content: space-evenly;
62 | align-items: center;
63 | width: 99%;
64 | margin: auto;
65 | height: 275px;
66 | }
67 |
68 | .linechart-container {
69 | /* style={{height: '240px', width: '50%'}} */
70 | height: 240px;
71 | width: 50%;
72 | }
73 |
74 | /* CHART STYLE */
75 | .graph-style {
76 | border-radius: 6px;
77 | box-shadow: 0px 0px 6px rgb(185, 185, 185);
78 | margin: 5px;
79 | cursor: pointer;
80 | }
81 |
82 | /* BOTTOM CONTENT */
83 | .bottom-content {
84 | display: flex;
85 | width: 99%;
86 | margin: auto;
87 | height: 375px;
88 | }
89 |
90 | /* .bottom-right-section{
91 | width: 50%;
92 | /* background-color: blue; */
93 |
94 | /* ERRORS DETAILS */
95 | .errors-title {
96 | width: 90%;
97 | font-size: 20px;
98 | padding: 10px;
99 | margin: auto;
100 | }
101 | .errors-details {
102 | display: flex;
103 | flex-direction: column;
104 | align-items:center;
105 | }
106 | ul {
107 | list-style-type: none;
108 | }
109 |
110 |
111 |
112 | @media screen and (max-width: 850px) {
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/dashboard/sidebar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | height: 100vh;
3 | }
4 | .sidebar-menu {
5 | display: flex;
6 | }
7 | .flakeguard-logo {
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: 10px;
12 | margin-top: 30px;
13 | }
14 | .footer-sidebar {
15 | display: flex;
16 | justify-content: space-evenly;
17 | align-items: center;
18 | font-weight: 600;
19 | border-top: 1px solid #c6d5e2;
20 | position: fixed;
21 | bottom: 0;
22 | width: 250px;
23 | margin-top: 0;
24 | }
25 |
26 | .footer-sidebar p {
27 | display: flex;
28 | justify-content: space-between;
29 | align-items: center;
30 | }
31 |
32 | .footer-sidebar span {
33 | margin-left: 5px;
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/decisionPage.css:
--------------------------------------------------------------------------------
1 | .decisionLogo {
2 | height: 90px;
3 | width: 180px;
4 | margin-left: 25px;
5 | }
6 |
7 | .decisionContainer {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | padding: 20px;
12 | }
13 |
14 | .info-text {
15 | margin-left: 10px;
16 | margin-bottom: 20px;
17 | color: #457f3e;
18 | font-size: 24px;
19 | }
20 |
21 | .decisionLogin:hover {
22 | transform: translateY(-3px) scale(1.05);
23 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
24 | }
25 |
26 | .flake-risk-container {
27 | margin-top: 20px;
28 | margin-bottom: 20px
29 | }
30 |
31 | .temp-text {
32 | margin-bottom: 10px;
33 | font-size: 18px;
34 | }
35 |
36 | .temp-button {
37 | margin-top: 0px;
38 | height: 30px
39 | }
40 |
41 | .temp-button:hover {
42 | background-color: #49ad2a;
43 | }
44 |
45 | .btn-text {
46 | font-size: 16px;
47 | }
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/docs.css:
--------------------------------------------------------------------------------
1 | .docs-container {
2 | padding: 20px;
3 | }
4 | .questions button {
5 | width: 280px;
6 | text-align: start;
7 | }
8 |
9 | .active {
10 | width: 600px;
11 | }
12 | .nav-pills .nav-link.active {
13 | background-color: rgb(248, 246, 246);
14 | color: #457f3e;
15 | }
16 | .nav-pills .nav-link {
17 | color: #49af3e;
18 | margin: 10px;
19 | }
20 | .tab-content .tab-pane {
21 | padding: 20px;
22 | border-left: 1px solid #ddd;
23 | border-radius: 0.25rem;
24 | height: 100vh;
25 | }
26 | .docs-title {
27 | color: #2b7023;
28 | }
29 |
30 | .docs-text{
31 | display: flex;
32 | justify-content: center;
33 | flex-direction: column;
34 | color: rgb(86, 90, 111);
35 | padding: 10px;
36 | }
37 |
38 | .docs-text li {
39 | width: 650px;
40 | color: rgb(86, 90, 111);
41 | margin: 8px;
42 | }
43 |
44 |
45 |
46 | /* FAQ */
47 | .faq-container {
48 | }
49 | .faq-container button{
50 | width: 600px;
51 | border-radius: 10px;
52 | }
53 | /* try to make the font bigger */
54 | .accordion-header:not(.collapsed) {
55 | border: none;
56 |
57 | }
58 |
59 | .accordion-header:not(.collapsed) {
60 | border-bottom: none;
61 | }
62 |
63 |
64 | .custom-bg {
65 | color: #000;
66 | }
67 |
68 | .custom-bg.collapsed {
69 | color: #000;
70 | border: none;
71 | }
72 |
73 | .custom-bg:not(.collapsed) {
74 | background-color: transparent;
75 | color: #000;
76 | }
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/footer.css:
--------------------------------------------------------------------------------
1 | .footer-container {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex-direction: column;
6 | height: 100px;
7 | background-color: #457f3e;
8 | font-size: 16px;
9 | }
10 |
11 | .footer-links a {
12 | color: white;
13 | }
14 | .footer-links {
15 | width: 40%;
16 | display: flex;
17 | justify-content: space-evenly;
18 | }
19 |
20 | .footer-icon-links {
21 | display: flex;
22 | justify-content: space-between;
23 | width: 10%;
24 | }
25 |
26 | .footer-icon-links a:hover {
27 | transform: scale(1.05);
28 | }
29 |
30 | @media screen and (max-width: 850px) {
31 | .footer-icon-links {
32 | width: 120px;
33 | }
34 |
35 | .footer-container {
36 | height: 160px;
37 | }
38 | .footer-links {
39 | display: flex;
40 | justify-content: center;
41 | align-items: center;
42 | flex-direction: column;
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | font-size: 16px;
6 | width: 95%;
7 | height: 80px;
8 | margin: auto;
9 | border-bottom: 1px solid #e6e7e8;
10 |
11 | }
12 | .header-container {
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | width: 450px;
17 | }
18 | .header-container Link {
19 | color: red;
20 | }
21 |
22 | a {
23 | text-decoration: none;
24 | color: black;
25 | }
26 | .login-btn {
27 | width: 200px;
28 | height: auto;
29 | padding: 5px;
30 | text-align: center;
31 | border-radius: 5px;
32 | background-color: #24292E;
33 | }
34 |
35 | /* from LoginButton componenent */
36 | .loginButton {
37 | display: flex;
38 | justify-content: space-evenly;
39 | align-items: center;
40 | width: 100%;
41 | border: none;
42 | background-color: none;
43 | background-color: #24292E;
44 |
45 | }
46 | .login-btn:hover {
47 | background-color: #323334;
48 | }
49 | .loginButton:hover {
50 | background-color: #323334;
51 | }
52 | .btn-text {
53 | color: white;
54 | font-size: 14px;
55 | }
56 |
57 | .dropdown-menu li {
58 | display: flex;
59 | justify-content: center;
60 | align-items: center;
61 | }
62 | .dropdown-menu button {
63 | background-color: none;
64 | }
65 | .btn-signout {
66 | background: none;
67 | color: inherit;
68 | border: none;
69 | padding: 0;
70 | font: inherit;
71 | cursor: pointer;
72 | outline: inherit;
73 | }
74 |
75 | /* signout button */
76 | .btn-signout {
77 | display: flex;
78 | justify-content: space-evenly;
79 | align-items: center;
80 | width: 60%;
81 | font-size: 15px;
82 | }
83 | .btn-text-signout {
84 | color: #24292E;
85 | }
86 | .btn-text-signout:hover {
87 | text-decoration: underline;
88 | }
89 |
90 |
91 | /* green 22C822 */
92 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/landingPage.css:
--------------------------------------------------------------------------------
1 | .landing-page-container {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | width: 75%;
6 | height: 550px;
7 | margin: auto;
8 | font-size: 20px;
9 | margin-top: 10px;
10 | }
11 | /* it's called left, because at some point i'd like to add something on the right side */
12 | .left-landing {
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | flex-direction: column;
17 | width: 580px;
18 | height: 450px;
19 | }
20 |
21 | .left-landing h1 {
22 | align-self: start;
23 | width: 98%;
24 | height: 120px;
25 | font-size: 40px;
26 | font-weight: 700;
27 | color: #457f3e;
28 | }
29 |
30 | .left-landing-text {
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | flex-direction: column;
35 | align-self: self-start;
36 | text-align: justify;
37 | }
38 |
39 | .npm-button {
40 | width: 100%;
41 | height: 40px;
42 | background-color: #4aa240;
43 | border: none;
44 | border-radius: 5px;
45 | margin-top: 35px;
46 | margin-bottom: 10px;
47 | }
48 | .npm-button:hover {
49 | background-color: #41aa35;
50 | box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
51 | }
52 | .npm-link {
53 | color: white;
54 | }
55 | .p-tag-with-link a:hover {
56 | color: #38932e;
57 | text-decoration: underline;
58 | }
59 |
60 | .right-landing {
61 | display: flex;
62 | justify-content: center;
63 | align-items: center;
64 | margin: auto;
65 | width: 750px;
66 | height: 100%;
67 | }
68 | .right-landing img {
69 | width:'750px';
70 | margin-left: '15px'
71 | }
72 |
73 | #medium-article-link{
74 | color: #4aa240;
75 | }
76 |
77 | /*TEAM CSS*/
78 |
79 | #avatars:hover{
80 | background-color: #457f3e;
81 | border-radius: 10px;
82 | box-shadow: 5px 5px 5px #457f3e;
83 | }
84 |
85 | .team {
86 | text-align: center;
87 | padding: 50px 0;
88 | color: #457f3e;
89 | }
90 |
91 | .team-members {
92 | display: flex;
93 | justify-content: space-evenly;
94 | flex-wrap: wrap; /* To handle different screen sizes */
95 | }
96 |
97 | .team-member {
98 | background-color: #fff;
99 | border-radius: 10px;
100 | box-shadow: 0px 2px 5px #457f3e;
101 | padding: 20px;
102 | text-align: center;
103 | width: 200px; /* Adjust width as needed */
104 | }
105 |
106 | .team-member-image {
107 | width: 100px;
108 | height: 100px;
109 | border-radius: 50%;
110 | object-fit: cover;
111 | }
112 |
113 | #linkedinlogo,
114 | #githublogo {
115 | width: 2rem;
116 | height: 2rem;
117 | margin: 0rem 1rem 0rem 1rem
118 | }
119 |
120 | @media screen and (max-width: 1500px) {
121 | .right-landing img {
122 | width: 650px;
123 | margin-left: 20px;
124 | }
125 | .landing-page-container {
126 | width: 85%;
127 | }
128 |
129 | }
130 |
131 | @media screen and (max-width: 1350px) {
132 | .left-landing {
133 | width: 70%;
134 | }
135 | .right-landing {
136 | display: none;
137 | }
138 | }
139 | @media screen and (max-width: 850px) {
140 | .left-landing {
141 | width: 100%;
142 | }
143 | .right-landing {
144 | display: none;
145 | }
146 | }
147 |
148 |
149 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/riskSign.css:
--------------------------------------------------------------------------------
1 | .flakeRiskContainer {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | background-color: rgb(121, 52, 52);
6 | color: rgb(255, 255, 255);
7 | width: 600px;
8 | border-radius: 20px;
9 | box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.3);
10 | }
11 |
12 | .flakeRiskContainer h1,
13 | .flakeRiskContainer h2,
14 | .flakeRiskContainer h2 em {
15 | color: rgb(230, 230, 230);
16 | background-color: rgb(121, 52, 52);;
17 | }
18 |
19 | .flakeRiskContainer h1{
20 | font-size: 40px;
21 | }
22 |
23 | .flakeRiskContainer h2 {
24 | padding-top: 20px;
25 | font-size: 24px;
26 | }
27 |
28 | .flakeRiskContainer h2 em {
29 | text-decoration: underline;
30 | }
31 |
32 | .flakeRiskSign {
33 | position: relative;
34 | width: 400px;
35 | height: 200px;
36 | border-top-left-radius: 200px;
37 | border-top-right-radius: 200px;
38 | overflow: hidden;
39 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
40 | }
41 |
42 | .flakeRiskSlice {
43 | position: absolute;
44 | left: 135px;
45 | top: 0%;
46 | width:130px;
47 | height:200px;
48 | clip-path:polygon(0% 0%, 100% 0%,50% 100%);
49 | border-radius:100px;
50 | transform-origin: 50% 100%;
51 | }
52 |
53 | .flakeRiskSlice:nth-child(1) {
54 | transform: rotate(-72deg);
55 | }
56 |
57 | .flakeRiskSlice:nth-child(2) {
58 | transform: rotate(-36deg);
59 | }
60 |
61 | .flakeRiskSlice:nth-child(3) {
62 | transform: rotate(0deg);
63 | }
64 |
65 | .flakeRiskSlice:nth-child(4) {
66 | transform: rotate(36deg);
67 | }
68 |
69 | .flakeRiskSlice:nth-child(5) {
70 | transform: rotate(72deg);
71 | }
72 |
73 | .flakeRiskSlice h3 {
74 | text-align: center;
75 | color: rgb(230, 230, 230);
76 | font-size: 18px;
77 | height: 50px;
78 | padding-top: 10px;
79 | text-shadow:
80 | -1px -1px 0 #000,
81 | 1px -1px 0 #000,
82 | -1px 1px 0 #000,
83 | 1px 1px 0 #000;
84 | }
85 |
86 | .flakeRiskArrow {
87 | position: relative;
88 | background-color: rgb(240, 240, 240);
89 | height: 160px;
90 | width: 20px;
91 | top: 50px;
92 | left: 190px;
93 | z-index: 10;
94 | border-radius: 10px;
95 | overflow: visible;
96 | transform-origin: 10px 150px;
97 | /* transform: rotate(70deg); */
98 | box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.3);
99 | }
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | * {
6 | color: rgb(0, 0, 0);
7 | }
8 |
9 | .intro-container {
10 | display: flex;
11 | justify-content: center; /* Centers content horizontally */
12 | align-items: center; /* Centers content vertically */
13 | text-align: center;
14 | position: relative;
15 | width: 100%;
16 | height: 35vh;
17 | }
18 |
19 | /* .codeScreen-container {
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | text-align: center;
24 | position: relative;
25 | width: 100%;
26 | height: 55vh;
27 | background-color: rgb(255, 255, 255);
28 | }
29 | */
30 |
31 | .doc-links-container {
32 | display: flex;
33 | justify-content: space-around;
34 | text-decoration: none;
35 | padding: 5px 10px;
36 | border-radius: 4px;
37 | transition: background-color 0.3s ease;
38 | }
39 | #github-logo:hover {
40 | transform: scale(
41 | 1.4,1.4
42 | ); /* Adjust the scale value as needed for the desired size */
43 | }
44 |
45 | #npm-logo:hover {
46 | transform: scale(
47 | 1.4,1.4
48 | ); /* Adjust the scale value as needed for the desired size */
49 | }
50 |
51 | .intro-background {
52 | width: 100%;
53 | height: 100%;
54 | /* object-fit: cover; */
55 | }
56 | #flake-logo {
57 | height: 35px;
58 | width: 35px;
59 | border-radius: 25%;
60 | }
61 |
62 |
63 | #social-logo {
64 | height: 35px;
65 | width: 35px;
66 | }
67 |
68 | #get-started:hover,
69 | #npm-button:hover{
70 | border: 5px solid rgb(138, 238, 99);
71 | border-radius: 10%;
72 | }
73 | #get-started, #npm-button{
74 | font-size: 20px;
75 | width: 100px;
76 | height: 45px;
77 | }
78 | #carousel-overlay {
79 | color: white;
80 | text-align: center;
81 | justify-content: center;
82 | align-items: center;
83 | }
84 |
85 | #feature-1-logo {
86 | width: 50px;
87 | height: 50px;
88 | }
89 | #carousel {
90 | border: 3px solid rgb(247, 204, 204);
91 | }
92 |
93 |
94 | .bg-image {
95 | position: absolute;
96 | top: 0;
97 | left: 0;
98 | width: 100%;
99 | height: 100%;
100 | object-fit: cover; /* Ensure the image covers the entire container */
101 | z-index: -1; /* Send the image to the background */
102 | }
103 |
104 | /* */
105 |
106 |
107 | #analytics-container {
108 | display: flex;
109 | justify-content: center;
110 | align-items: center;
111 | height: 50vh; /* Ensures full viewport height for centering */
112 | }
113 |
114 | /* .assertions-graph:hover, .summary-dash:hover, .errors-dash:hover, .trends-dash:hover{
115 | transition: transform 0.3s ease;
116 | transform: scale(1.1, 1.1);
117 | } */
118 |
119 | #dashboard-header, .trends-dash{
120 | margin: 30px;
121 | }
122 |
123 | #sign-in-button:hover{
124 | border: 5px solid rgb(138, 238, 99);
125 | border-radius: 5%;
126 | transition: transform 0.3s ease;
127 | }
128 |
129 | /*LANDING*/
130 |
131 |
132 | /****************************************************/
133 |
134 | /*navBar*/
135 |
136 | #navbar-container .navbar-custom {
137 | width: 100vh;
138 | }
139 |
140 | /****************************************************/
141 |
142 | /*footer*/
143 |
144 | .footer-container {
145 | background-size: cover;
146 | background-position: center;
147 | padding: 20px;
148 | display: flex;
149 | justify-content: center;
150 | align-items: center;
151 | }
152 |
153 | .footer-div {
154 | display: flex;
155 | justify-content: space-between;
156 | align-items: center;
157 | width: 200px; /* Adjust this width as needed */
158 | }
159 | .footer-div img {
160 | transition: transform 0.3s ease; /* Smooth transition for the size adjustment */
161 | }
162 |
163 |
164 | /****************************************************/
165 |
166 | /*Carousel*/
167 |
168 | #carousel-card {
169 | display: flex;
170 | flex-direction: column;
171 | align-items: center;
172 | justify-content: center;
173 | text-align: center;
174 | color: white;
175 | padding: 20px;
176 | height: 30vh;
177 | }
178 |
179 | #carousel-container {
180 | display: flex;
181 | justify-content: space-between;
182 | width: 100%;
183 | max-width: 500px; /* Adjust as needed */
184 | margin-top: 10px;
185 | }
186 | /* .code-overlay {
187 | box-shadow: 10px 5px 5px red;
188 | } */
189 | .main-container {
190 | flex: 1;
191 | display: flex;
192 | flex-direction: column;
193 | justify-content: center; /* Center vertically */
194 | align-items: center; /* Center horizontally */
195 | padding-top: 5%; /* Adjust this value to move the content just below the center */
196 | }
197 | .intro-bg {
198 | box-shadow: 10px 10px 50px 10px rgb(238, 99, 99);
199 | transition: transform 0.3s ease; /* Smooth transition for the grow effect */
200 | background-color: #ffffff;
201 | color: #000000;
202 | width: fit-content;
203 | }
204 |
205 | .intro-bg:hover {
206 | box-shadow: 20px 20px 100px 20px #000000; /* Scale the container to 110% of its original size */
207 | }
208 | #carousel-prev,
209 | #carousel-next {
210 | background-color: white;
211 | color: black;
212 | border: none;
213 | /* padding: 10px 20px; */
214 | margin: 10px;
215 | cursor: pointer;
216 | width: 100px;
217 | height: 45px;
218 | font-size: 20px;
219 | }
220 |
221 | #carousel-next {
222 | text-align: center;
223 | }
224 | #carousel-prev:hover,
225 | #carousel-next:hover {
226 | border: 5px solid rgb(138, 238, 99);
227 | border-radius: 10%;
228 | }
229 |
230 | #carousel-overlay{
231 | color: rgb(0, 0, 0);
232 | font-size: 30px;
233 | }
234 | #tagline-emphasize{
235 | color: rgb(0, 0, 0);
236 | font-size: 18px;
237 | padding: 250px;
238 | }
239 |
240 |
241 | /****************************************************/
242 |
243 | /*MEDIA QUERY*/
244 |
245 | /****************************************************/
246 | /* @media only screen and (max-width: 800px) {
247 |
248 | } */
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/tailwind-output.css:
--------------------------------------------------------------------------------
1 | * {
2 | background-color: rgb(235, 235, 235);
3 | color: rgb(0, 0, 0);
4 | }
5 |
6 | #navbar-container {
7 | width: 100vh;
8 | /*not currently making width 100%????*/
9 | }
10 |
11 | #intro-title {
12 | font-weight: bold;
13 | font-size: 60px;
14 | font-family: Georgia, serif;
15 | text-align: center;
16 | /* Centers text within the h1 */
17 | margin-bottom: 20px;
18 | }
19 |
20 | .intro-container {
21 | display: flex;
22 | justify-content: center;
23 | /* Centers content horizontally */
24 | align-items: center;
25 | /* Centers content vertically */
26 | text-align: center;
27 | padding: 20px;
28 | }
29 |
30 | #flake-logo {
31 | height: 35px;
32 | width: 35px;
33 | border-radius: 25%;
34 | }
35 |
36 | #tagline-emphasize {
37 | background: linear-gradient(
38 | to right,
39 | #00a86b,
40 | #6610f2
41 | );
42 | /* Jade to Indigo gradient */
43 | -webkit-background-clip: text;
44 | background-clip: text;
45 | /* Standard */
46 | color: transparent;
47 | }
48 |
49 | #social-logo {
50 | height: 35px;
51 | width: 35px;
52 | }
53 |
54 | .card {
55 | width: 18rem;
56 | }
57 |
58 | .cards-container{
59 | display: flex;
60 | justify-content: center;
61 | /* Centers content horizontally */
62 | align-items: center;
63 | /* Centers content vertically */
64 | }
65 |
66 | #feature-1-logo{
67 | width: 50px;
68 | height: 50px;
69 | }
70 |
71 | #carousel{
72 | border: 3px solid black;
73 | }
--------------------------------------------------------------------------------
/flake-guard-app/src/client/styles/tempDash.css:
--------------------------------------------------------------------------------
1 | .tempDashboardContainer {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .tempDashFirstRow {
8 | display: flex;
9 | justify-content: space-around;
10 | align-items: center;
11 | }
12 |
13 | .tempDashSummary {
14 | width: 30vw;
15 | }
16 |
17 | .tempDashSecondRow {
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | }
22 |
23 | .tempDashBar {
24 | width: 100vw;
25 | }
26 |
27 | .temp-flakiness-box {
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | align-items: center;
32 | justify-content: space-around;
33 | }
34 |
35 | .flakyRate {
36 | width: 20%;
37 | margin-left: 10px;
38 | margin-right: 10px;
39 | }
40 |
41 | .barChart {
42 | width: 60%;
43 | height: 95%;
44 | margin-right: 10px;
45 | }
46 |
47 | .tempContainer {
48 | width: 100%;
49 | display: flex;
50 | justify-content: center;
51 | align-items: center;
52 | background-color: white;
53 | height: 400px;
54 | border-radius: 10px;
55 | box-shadow: 0px 0px 20px grey;
56 | margin: 10px;
57 | }
--------------------------------------------------------------------------------
/flake-guard-app/src/client/supabaseClient.ts:
--------------------------------------------------------------------------------
1 | import {createClient} from '@supabase/supabase-js';
2 |
3 | const supabaseClient = createClient(
4 | 'https://loaqgsdpuktznsvfjtbr.supabase.co',
5 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxvYXFnc2RwdWt0em5zdmZqdGJyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTcyNjUyMTEsImV4cCI6MjAzMjg0MTIxMX0.2dYTwTgfLhw9A9ACngtYZva0_nNy3-QwnzYPdX_dBHk'
6 | );
7 |
8 | export {supabaseClient};
9 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/DecisionPage.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react';
2 | import {useNavigate, useParams} from 'react-router-dom';
3 | import {supabaseClient} from '../supabaseClient';
4 | import {api} from '../services/index';
5 | import github from '../assets/github-mark-white.png';
6 | import FlakeRiskContainer from '../flakeRiskSign/FlakeRiskSign/FlakeRiskContainer';
7 | import {calculateFlakePercentage} from '../flakeRiskSign/Analytics/flake-percentage';
8 | import logo from '../assets/logo.png';
9 | import '../styles/decisionPage.css'
10 | import LoginButton from '../landingPage/components/LoginButton';
11 |
12 | const DecisionPage: React.FC = () => {
13 | const [loggedIn, setLoggedIn] = useState(false);
14 | const navigate = useNavigate();
15 | const {id} = useParams();
16 | const [flakePercent, setFlakePercent] = useState(undefined);
17 |
18 | useEffect(() => {
19 | const fetchMetrics = async () => {
20 | try {
21 | const response = await api.get(`/tempDash/${id}`);
22 | const results = response.data;
23 | const flakePercentage = calculateFlakePercentage(results);
24 | setFlakePercent(flakePercentage);
25 | } catch (error) {
26 | console.error('Error fetching data:', error);
27 | }
28 | };
29 |
30 | fetchMetrics();
31 | }, []);
32 |
33 | useEffect(() => {
34 | const checkIfLoggedIn = async () => {
35 | try {
36 | const {data, error} = await supabaseClient.auth.getUser();
37 |
38 | if (data && !error) {
39 | // get results from cache and save to db
40 | const response = await api.get(`/results/${id}`);
41 | const results = response.data;
42 | await api.post(`/userDash/${data.user.id}`, {results});
43 | await api.delete(`/tempDash/${id}`);
44 | // route to user dashboard
45 | const url: string = `/dashboard/user/${data.user.id}`;
46 | navigate(url);
47 | }
48 | } catch (error) {
49 | console.error(
50 | 'Error checking user auth or getting results from cache and saving to database: ',
51 | error
52 | );
53 | }
54 | };
55 | checkIfLoggedIn();
56 | }, [loggedIn]);
57 |
58 | const signIn = async () => {
59 | try {
60 | const signInResponse = await supabaseClient.auth.signInWithOAuth({
61 | provider: 'github',
62 | options: {redirectTo: `https://flakeguard.com/npm/${id}`},
63 | });
64 | console.log('SIGN IN RESPONSE------>', signInResponse);
65 | const {data, error} = await supabaseClient.auth.getUser();
66 | if (data.user && !error) setLoggedIn(true);
67 | } catch (error) {
68 | console.error('Error signing in: ', error);
69 | }
70 | };
71 |
72 | const goToTemp = (): void => {
73 | navigate(`/tempdashboard/${id}`);
74 | };
75 |
76 | return (
77 | <>
78 |
83 |
84 |
To view detailed metrics and save your results:
85 |
86 |
87 |
88 | Sign in with GitHub
89 |
90 |
91 |
92 |
93 |
94 |
To view a temporary dashboard without signing in:
95 |
goToTemp()} style={{width: '100px'}}>
96 | Click here
97 |
98 |
99 | >
100 | );
101 | };
102 |
103 | export default DecisionPage;
104 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/TempDashboard.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react';
2 | import {api} from '../services/index';
3 | import '../styles/dashboard.css';
4 | import Summary from './components/Summary';
5 | import AssertionsGraph from './components/AssertionsGraph';
6 | import DisplayErrors from './components/DisplayErrors';
7 | import {useParams} from 'react-router-dom';
8 | import NavBarHeading from '../landingPage/components/NavBar';
9 | import Footer from '../landingPage/components/Footer';
10 | import {calculateFlakePercentage} from '../flakeRiskSign/Analytics/flake-percentage';
11 | import FlakeRiskContainer from '../flakeRiskSign/FlakeRiskSign/FlakeRiskContainer';
12 | import '../styles/tempDash.css';
13 |
14 | const TempDashboard = (): JSX.Element => {
15 | const [metrics, setMetrics] = useState<{[key: string]: number} | undefined>(
16 | undefined
17 | );
18 | const [fetchResults, setFetchResults] = useState([]);
19 | const [flakePercentage, setFlakePercentage] = useState(
20 | undefined
21 | );
22 |
23 | const {id} = useParams();
24 |
25 | useEffect(() => {
26 | const fetchMetrics = async () => {
27 | try {
28 | // remove get request from db - will add conditional later for permanent users
29 | // const response = await api.get('/results/');
30 |
31 | const response = await api.get(`/tempDash/${id}`);
32 | await api.delete(`/tempDash/${id}`);
33 | const results = response.data;
34 | console.log('retrieved cached results', results);
35 | setFetchResults(results);
36 |
37 | const flakePercentage = calculateFlakePercentage(results); // Calculate flake percentage
38 | console.log('FLAKE PERCENTAGE--->', flakePercentage);
39 | setFlakePercentage(flakePercentage); // Set flakePercentage state
40 |
41 | let totalPassed = 0;
42 | let totalFailed = 0;
43 | let totalSkipped = 0;
44 |
45 | for (const result of results) {
46 | totalPassed += result.passed;
47 | totalFailed += result.failed;
48 | totalSkipped += result.skipped;
49 | }
50 |
51 | const labelsArr = {
52 | passed: totalPassed,
53 | failed: totalFailed,
54 | skipped: totalSkipped,
55 | };
56 |
57 | setMetrics(labelsArr); // Set metrics state
58 |
59 | console.log('METRICS--->', metrics);
60 | } catch (error) {
61 | console.error('Error fetching data:', error);
62 | }
63 | };
64 |
65 | fetchMetrics();
66 | }, []);
67 |
68 | return (
69 | <>
70 |
71 |
72 |
73 |
74 |
75 | {metrics && }
76 |
77 |
78 |
79 |
80 |
81 |
84 |
85 |
86 | >
87 | );
88 | };
89 |
90 | export default TempDashboard;
91 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/components/AssertionsGraph.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import {
4 | Chart as ChartJS,
5 | CategoryScale,
6 | LinearScale,
7 | BarElement,
8 | Title,
9 | Tooltip,
10 | Legend,
11 | } from 'chart.js';
12 | import {Bar} from 'react-chartjs-2';
13 | import Flakiness from './Flakiness';
14 | import '../../styles/tempDash.css'
15 |
16 | ChartJS.register(
17 | CategoryScale,
18 | LinearScale,
19 | BarElement,
20 | Title,
21 | Tooltip,
22 | Legend
23 | );
24 |
25 | interface MetricsData {
26 | fullName: string;
27 | passed: number;
28 | failed: number;
29 | skipped: number;
30 | }
31 |
32 | interface AssertionsGraphProps {
33 | fetchResults: MetricsData[] | undefined;
34 | }
35 |
36 | const AssertionsGraph: React.FC = ({fetchResults}) => {
37 | const barChartData = {
38 | labels: [] as string[],
39 | datasets: [
40 | {
41 | label: 'Passed',
42 | data: [] as number[],
43 | backgroundColor: 'rgba(81, 209, 156, 0.6)',
44 | hoverOffset: 1,
45 | barThickness: 25,
46 | },
47 | {
48 | label: 'Failed',
49 | data: [] as number[],
50 | backgroundColor: 'rgba(255, 49, 49, .9)',
51 | hoverOffset: 1,
52 | barThickness: 25,
53 | },
54 | ],
55 | };
56 |
57 | const flaggedIndices = new Set();
58 |
59 | if (fetchResults) {
60 | for (let i = 0; i < fetchResults.length; i++) {
61 | const results = fetchResults[i];
62 | console.log('fetchResults ---> ', results);
63 |
64 | barChartData.labels.push('assertion ' + (i + 1));
65 | barChartData.datasets[0].data.push(results.passed);
66 | barChartData.datasets[1].data.push(results.failed);
67 |
68 | if (results.passed > 0 && results.failed > 0) {
69 | flaggedIndices.add(i);
70 | }
71 | }
72 | }
73 |
74 | const options = {
75 | indexAxis: 'y' as const,
76 | responsive: true,
77 | plugins: {
78 | legend: {
79 | display: true,
80 | },
81 | title: {
82 | display: true,
83 | text: 'Assertion Results',
84 | font: {
85 | size: 18,
86 | },
87 | },
88 | },
89 | scales: {
90 | x: {
91 | stacked: true,
92 | },
93 | y: {
94 | stacked: true,
95 | ticks: {
96 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
97 | callback: function (value: any, index: number, values: any) {
98 | if (flaggedIndices.has(index)) {
99 | return `* ${barChartData.labels[index]} `;
100 | }
101 | return barChartData.labels[index];
102 | },
103 | },
104 | },
105 | },
106 | };
107 |
108 | return (
109 |
110 |
111 |
112 |
Flaky Rate (%)
113 |
114 | {fetchResults &&
115 | fetchResults.map(result => (
116 |
117 |
118 |
119 | ))}
120 |
121 |
122 |
123 |
124 |
125 |
126 | );
127 | };
128 |
129 | export default AssertionsGraph;
130 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/components/DisplayErrors.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import arrow from '../../assets/arrow.png';
3 |
4 | interface MetricsData {
5 | fullName: string;
6 | passed: number;
7 | failed: number;
8 | skipped: number;
9 | }
10 |
11 | interface AssertionsGraphProps {
12 | fetchResults: MetricsData[] | undefined;
13 | }
14 |
15 | const DisplayErrors: React.FC = ({fetchResults}) => {
16 | const [showDetails, setShowDetails] = useState([]);
17 | const [arrowDirections, setArrowDirections] = useState([]);
18 |
19 | const handleToggleDetails = (index: number) => {
20 | const updatedShowDetails = [...showDetails];
21 | updatedShowDetails[index] = !updatedShowDetails[index];
22 | setShowDetails(updatedShowDetails);
23 |
24 | const updatedArrowDirections = [...arrowDirections];
25 | updatedArrowDirections[index] = !updatedArrowDirections[index];
26 | setArrowDirections(updatedArrowDirections);
27 | };
28 |
29 | return (
30 |
31 |
Test Results
32 |
33 |
34 |
ERROR
35 | {fetchResults ? (
36 |
37 | {fetchResults.map((result, index) => (
38 |
39 |
50 | {
52 | handleToggleDetails(index);
53 | }}
54 | >
55 | {result.fullName}
56 |
57 |
58 | {showDetails[index] && (
59 |
60 |
Passed: {result.passed}
61 |
Failed: {result.failed}
62 |
Skipped: {result.skipped}
63 |
64 | )}
65 |
66 |
67 | ))}
68 |
69 | ) : (
70 |
No data available
71 | )}
72 |
73 |
74 | );
75 | };
76 |
77 | export default DisplayErrors;
78 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/components/Flakiness.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Chart as ChartJS,
4 | CategoryScale,
5 | LinearScale,
6 | BarElement,
7 | Title,
8 | Tooltip,
9 | Legend,
10 | } from 'chart.js';
11 | import {Bar} from 'react-chartjs-2';
12 | import {ProgressBar} from 'react-bootstrap';
13 |
14 | ChartJS.register(
15 | CategoryScale,
16 | LinearScale,
17 | BarElement,
18 | Title,
19 | Tooltip,
20 | Legend
21 | );
22 |
23 |
24 | interface AssertionsGraphProps {
25 | fetchResults: number;
26 | }
27 |
28 | const Flakiness: React.FC = ({fetchResults}) => {
29 | console.log('fetchResults ---> ', fetchResults);
30 | return (
31 |
32 |
{`${fetchResults}%`}}
37 | />
38 |
39 | );
40 | };
41 |
42 | export default Flakiness;
43 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/tempDash/components/Summary.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Doughnut} from 'react-chartjs-2';
3 | import {Chart as ChartJS, Tooltip, Legend, ArcElement, Title} from 'chart.js';
4 | import 'chartjs-plugin-datalabels';
5 |
6 | ChartJS.register(Tooltip, Legend, ArcElement, Title);
7 |
8 | interface MetricsData {
9 | [key: string]: number;
10 | }
11 |
12 | interface SummaryProps {
13 | metrics: MetricsData | undefined;
14 | }
15 |
16 | const Summary: React.FC = ({metrics}) => {
17 | //DATA
18 | const pieChartData = {
19 | labels: [''],
20 | datasets: [
21 | {
22 | label: 'Time Spent',
23 | data: [0, 0, 0],
24 | backgroundColor: [
25 | 'rgba(81, 209, 156)',
26 | 'rgba(250, 88, 102)',
27 | 'rgba(208, 211, 211)',
28 | ],
29 | hoverOffset: 4,
30 | },
31 | ],
32 | };
33 |
34 | let total = 0;
35 | if (metrics) {
36 | const passed = metrics.passed;
37 | const failed = metrics.failed;
38 | const skipped = metrics.skipped;
39 | total = passed + failed + skipped;
40 | pieChartData.datasets[0].data[0] = passed;
41 | pieChartData.datasets[0].data[1] = failed;
42 | pieChartData.datasets[0].data[2] = skipped;
43 |
44 | pieChartData.labels[0] = `Passed ${passed}/${total}`;
45 | pieChartData.labels[1] = `Failed ${failed}/${total}`;
46 | pieChartData.labels[2] = `Skipped ${skipped}/${total}`;
47 | }
48 |
49 | //OPTIONS
50 | const options = {
51 | cutout: '90%',
52 | responsive: true,
53 | plugins: {
54 | legend: {
55 | position: 'bottom' as const,
56 | align: 'center' as const,
57 | labels: {
58 | usePointStyle: true,
59 | color: '#474646',
60 | },
61 | },
62 | textInside: {
63 | text: `${total} assertions`,
64 | color: '#474646',
65 | fontSize: 20,
66 | },
67 | title: {
68 | display: true,
69 | text: 'Summary',
70 | font: {
71 | size: 18,
72 | },
73 | },
74 | },
75 | };
76 | //Configuration of the plugin from line 60. Allows display the total of assertions (text)
77 | const textInsidePlugin = {
78 | id: 'textInside',
79 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
80 | afterDatasetsDraw: function (chart: any) {
81 | const ctx = chart.ctx;
82 | const width = chart.width;
83 | const height = chart.height;
84 | const fontSize = options.plugins.textInside.fontSize;
85 | ctx.font = fontSize + 'px Arial';
86 | ctx.fillStyle = options.plugins.textInside.color;
87 | ctx.textAlign = 'center';
88 | ctx.textBaseline = 'middle';
89 | const text = options.plugins.textInside.text;
90 | const textX = Math.round(width / 2);
91 | const textY = Math.round(height / 2);
92 | ctx.fillText(text, textX, textY);
93 | },
94 | };
95 | const backgroundColorPlugin = {
96 | id: 'customCanvasBackgroundColor',
97 | beforeDraw: (
98 | chart: ChartJS<'doughnut'>,
99 | args: any,
100 | options: {color?: string}
101 | ) => {
102 | const {ctx} = chart;
103 | ctx.save();
104 | ctx.globalCompositeOperation = 'destination-over';
105 | ctx.fillStyle = options.color || '#ffffff';
106 | ctx.fillRect(0, 0, chart.width, chart.height);
107 | ctx.restore();
108 | },
109 | };
110 | ChartJS.register(backgroundColorPlugin);
111 |
112 | const plugins = [textInsidePlugin, backgroundColorPlugin];
113 |
114 | return (
115 |
116 |
117 |
118 | );
119 | };
120 |
121 | export default Summary;
122 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/types.ts:
--------------------------------------------------------------------------------
1 | // src/client/types.ts
2 | export interface TestResult {
3 | name: string;
4 | passed: number;
5 | failed: number;
6 | skipped: number;
7 | }
8 |
9 | export interface AssertionResult {
10 | name: string;
11 | passed: boolean;
12 | failed: boolean;
13 | }
14 |
15 | export interface MetricsData {
16 | fullName: string;
17 | passed: number;
18 | failed: number;
19 | skipped: number;
20 | }
21 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/UserDashboard.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | import React, {useEffect, useState, useContext} from 'react';
4 | import Sidebar from './components/Sidebar';
5 | import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
6 | import '../styles/dashboard/newDashboard.css';
7 | import PieChart from './components/pie/PieChart';
8 | import Calendar from './components/calendar/Calendar';
9 | import BarChart from './components/bar/BarChart';
10 | import ErrorsDetails from './components/errorsDetails/ErrorsDetails';
11 | import {CalendarData} from './components/calendar/data'; // data for Calendar
12 | import LineChart from './components/line/LineChart';
13 | import {flakyDataParser} from '../utilities/flakyDataParser';
14 | import {OverlayTrigger, Tooltip} from 'react-bootstrap';
15 | import LoginButton from '../landingPage/components/LoginButton';
16 | import Duration from './components/duration/Duration';
17 | import { ResultsContext } from './contexts/ResultContext';
18 | import { barChartParser } from '../utilities/barchartDataParser';
19 |
20 | const UserDashboard: React.FC = () => {
21 | const [flakytData, setFlakyData] = useState([]);
22 | //Uses the state from the ResultsContext
23 | const results = useContext(ResultsContext)
24 |
25 | // Parses data for the bar chart
26 | const [barChartData, setBarChartData] = useState([]);
27 | useEffect(() => {
28 | const chartData = barChartParser(results);
29 | const latestRun = chartData.slice(-20); //Display the last 20 runs
30 | // console.log('Parsed BAR Chart Data:', latestRun);
31 | if (Array.isArray(latestRun)) setBarChartData(latestRun);
32 | }, [results]);
33 |
34 |
35 |
36 | // Data for 'Flakiness and Always Failing' boxes
37 | useEffect(() => {
38 | const chartData = flakyDataParser(results);
39 | if (Array.isArray(chartData)) {
40 | const latestRun = chartData[chartData.length - 1]; // outputs latest run
41 | if (latestRun) {
42 | // Adding leading zero to number less than 10
43 | if (latestRun.flaky < 10) {
44 | latestRun.flaky = latestRun.flaky.toString().padStart(2, '0');
45 | latestRun.alwaysFail = latestRun.alwaysFail
46 | .toString()
47 | .padStart(2, '0');
48 | latestRun.totalTests = latestRun.totalTests
49 | .toString()
50 | .padStart(2, '0');
51 | }
52 | }
53 | setFlakyData(latestRun);
54 | }
55 | }, [results]);
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 |
DASHBOARD
65 |
66 |
67 |
68 |
76 |
77 |
78 |
79 |
Flakiness
80 |
83 | Tests that occasionally fails without any changes in the
84 | codebase, indicating unreliable behavior.
85 |
86 | }
87 | placement="right"
88 | >
89 |
90 |
91 |
92 |
93 |
94 | {flakytData && (
95 |
96 |
{flakytData.flaky}
97 |
98 | {flakytData.flaky} out of {flakytData.totalTests} tests are
99 | flaky
100 |
101 |
102 | )}
103 |
104 |
105 |
106 |
Always failing
107 |
110 | Consistently produces a failure result every time it is
111 | executed, indicating a persistent issue in the code or
112 | test setup.
113 |
114 | }
115 | placement="right"
116 | >
117 |
118 |
119 |
120 |
121 |
122 | {flakytData && (
123 |
124 |
{flakytData.alwaysFail}
125 |
126 | {flakytData.alwaysFail} out of {flakytData.totalTests} tests
127 | are always failing
128 |
129 |
130 | )}
131 |
132 |
133 |
141 |
142 |
143 | {/* BOTTOM CONTENT */}
144 |
145 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
Logs
159 |
160 |
161 |
162 |
163 |
170 |
171 |
172 |
173 | );
174 | };
175 |
176 | export default UserDashboard;
177 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Sidebar, Menu, MenuItem } from 'react-pro-sidebar';
3 | import { Link } from 'react-router-dom';
4 | import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
5 | import PercentOutlinedIcon from '@mui/icons-material/PercentOutlined';
6 | import BugReportOutlinedIcon from '@mui/icons-material/BugReportOutlined';
7 | import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';
8 | import MenuBookOutlinedIcon from '@mui/icons-material/MenuBookOutlined';
9 | import HistoryOutlinedIcon from '@mui/icons-material/HistoryOutlined';
10 | import '../../styles/dashboard/sidebar.css';
11 | import logo from '../../assets/logo.png';
12 | import { useParams } from 'react-router-dom';
13 | import { api } from '../../services';
14 |
15 | const MenuSidebar: React.FC = () => {
16 | const { userId } = useParams();
17 | const [results, setResults] = useState([]);
18 |
19 | useEffect(() => {
20 | const getResults = async () => {
21 | try {
22 | const results = await api.get(`/userDash/${userId}`);
23 | const resultsArray = results.data;
24 | // add a yyyy-mm-dd date to each result
25 | for (const result of resultsArray) {
26 | const ts = result.created_at;
27 | result.date = ts.slice(0, ts.indexOf('T'));
28 | }
29 | setResults(resultsArray);
30 | } catch (error) {
31 | console.log('Error getting results: ', error);
32 | }
33 | };
34 | getResults();
35 | }, [userId]);
36 |
37 |
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Dashboard
51 |
52 |
53 |
54 |
55 |
56 | Code Coverage
57 |
58 |
59 |
60 |
61 |
62 | Flaky Tests
63 |
64 |
65 |
66 |
67 |
68 | History
69 |
70 |
71 |
72 |
73 |
74 |
75 | Support
76 |
77 |
78 |
79 |
80 | Documentation
81 |
82 |
83 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default MenuSidebar;
90 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/bar/BarChart.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | import { useState, useEffect } from 'react';
4 | import { ResponsiveBar } from '@nivo/bar';
5 | import { barChartParser } from '../../../utilities/barchartDataParser';
6 |
7 | const BarChart: React.FC = ({ barChartData }) => {
8 | // const [barChartData, setBarChartData] = useState([]);
9 |
10 | // useEffect(() => {
11 | // const chartData = barChartParser(results);
12 | // const latestRun = chartData.slice(-20); //Display the last 20 runs
13 | // // console.log('Parsed BAR Chart Data:', latestRun);
14 | // if (Array.isArray(latestRun)) setBarChartData(latestRun);
15 | // }, [results]);
16 |
17 | return (
18 | {
27 | switch (id) {
28 | case 'passed':
29 | return data.passedColor || 'green';
30 | case 'failed':
31 | return data.failedColor || 'red';
32 | case 'skipped':
33 | return data.skippedColor || 'grey';
34 | default:
35 | return 'black';
36 | }
37 | }}
38 | borderColor={{
39 | from: 'color',
40 | modifiers: [['darker', 1.6]]
41 | }}
42 | axisTop={null}
43 | axisRight={null}
44 | axisBottom={{
45 | tickSize: 5,
46 | tickPadding: 5,
47 | tickRotation: 0,
48 | legend: 'tests (id)',
49 | legendPosition: 'middle',
50 | legendOffset: 32,
51 | truncateTickAt: 0
52 | }}
53 | axisLeft={{
54 | tickSize: 5,
55 | tickPadding: 5,
56 | tickRotation: 0,
57 | legend: 'runs',
58 | legendPosition: 'middle',
59 | legendOffset: -40,
60 | truncateTickAt: 0
61 | }}
62 | labelSkipWidth={12}
63 | labelSkipHeight={12}
64 | labelTextColor={{ from: 'color', modifiers: [['darker', 1.6]] }}
65 | legends={[
66 | {
67 | dataFrom: 'keys',
68 | anchor: 'bottom-right',
69 | direction: 'column',
70 | justify: false,
71 | translateX: 120,
72 | translateY: 0,
73 | itemsSpacing: 2,
74 | itemWidth: 100,
75 | itemHeight: 20,
76 | itemDirection: 'left-to-right',
77 | itemOpacity: 0.85,
78 | symbolSize: 20,
79 | effects: [
80 | {
81 | on: 'hover',
82 | style: {
83 | itemOpacity: 1
84 | }
85 | }
86 | ]
87 | }
88 | ]}
89 | role="application"
90 | ariaLabel="Nivo bar chart demo"
91 | barAriaLabel={e => `${e.id}: ${e.formattedValue} in country: ${e.indexValue}`}
92 | />
93 | );
94 | };
95 |
96 | export default BarChart;
97 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/bar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Sidebar from '../Sidebar';
3 | import '../../../styles/dashboard/charts.css'
4 |
5 | const BarChartPage: React.FC = () => {
6 | // console.log('ata from indexedDB', CalendarData);
7 | return (
8 |
9 |
10 |
11 | );
12 | };
13 |
14 | export default BarChartPage;
15 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/calendar/Calendar.tsx:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import React, { useContext, useEffect, useState } from 'react';
3 | import { ResponsiveCalendar } from '@nivo/calendar';
4 | import { ResultsContext } from '../../contexts/ResultContext';
5 | import { flakyDataParser } from '../../../utilities/flakyDataParser';
6 |
7 | interface DataItem {
8 | value: number;
9 | day: string;
10 | }
11 |
12 | interface DataProps {
13 | CalendarData: DataItem[];
14 | }
15 |
16 | const Calendar: React.FC = () => {
17 | const [calendarData, setCalendarData] = useState([]);
18 | const results = useContext(ResultsContext);
19 |
20 | useEffect(() => {
21 | const chartData = flakyDataParser(results);
22 | const dataMap: { [key: string]: number } = {};
23 |
24 | chartData.forEach((item: { date: string; flaky: number }) => {
25 | dataMap[item.date] = (dataMap[item.date] || 0) + item.flaky;
26 | });
27 |
28 | const calendarDataArray = Object.keys(dataMap).map(date => ({
29 | day: date,
30 | value: dataMap[date]
31 | }));
32 |
33 | setCalendarData(calendarDataArray);
34 |
35 | }, [results]);
36 |
37 | // console.log('results Calendar', calendarData);
38 |
39 | return (
40 |
41 |
Flaky Test Frequency by Day
42 | {calendarData.length > 0 &&
43 |
67 | }
68 |
69 | );
70 | };
71 |
72 | export default Calendar;
73 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/calendar/data.ts:
--------------------------------------------------------------------------------
1 | export const CalendarData = [
2 | { day: '2024-01-01', value: 5 },
3 | { day: '2024-01-02', value: 20 },
4 | { day: '2024-01-03', value: 50 },
5 | { day: '2024-01-04', value: 80 },
6 | ];
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/duration/Duration.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import React, { useEffect, useState } from 'react';
3 | import { displayErrorsDataParser } from '../../../utilities/displayErrorsDataParser';
4 | import '../../../styles/dashboard/charts.css';
5 |
6 | import {
7 | ComposedChart,
8 | Line,
9 | Bar,
10 | XAxis,
11 | YAxis,
12 | Tooltip,
13 | CartesianGrid,
14 | ResponsiveContainer,
15 | } from 'recharts';
16 |
17 |
18 | const Duration = ({ results }) => {
19 | const [data, setData] = useState([]);
20 |
21 | useEffect(() => {
22 | const chartData = displayErrorsDataParser(results);
23 | const latestRun = chartData.slice(-1); // Display the latest run
24 | const latestRunObj = latestRun[0];
25 |
26 | if (latestRunObj) {
27 | const formattedData = latestRunObj.durations.map((duration, index) => ({
28 | name: `${index + 1}`,
29 | ms: duration
30 | }));
31 | setData(formattedData);
32 | }
33 | }, [results]);
34 |
35 | // console.log('dataaaa', data);
36 | return (
37 |
39 |
Execution duration (ms)
40 |
41 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default Duration;
65 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/errorsDetails/ErrorsDetails.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | import React, {useState, useEffect} from "react";
4 | import '../../../styles/dashboard/newDashboard.css'
5 | import {displayErrorsDataParser} from '../../../utilities/displayErrorsDataParser'
6 | import Accordion from 'react-bootstrap/Accordion';
7 |
8 | const ErrorsDetails: React.FC = ({results}) => {
9 | const [errorsDetails, setErrorsDetails] = useState([])
10 | const [metrics, setMetrics] = useState([]);
11 |
12 | useEffect(() => {
13 | const chartData = displayErrorsDataParser(results);
14 | const latestRun = chartData.slice(-1); //Display the latest run
15 | // console.log('chartData', chartData)
16 | if (latestRun.length > 0) {
17 | const latestRunMetrics = latestRun[0]
18 | setMetrics(latestRunMetrics.details)
19 | }
20 | if (Array.isArray(latestRun)) setErrorsDetails(latestRun);
21 | }, [results]);
22 |
23 | // console.log('details', errorsDetails, metrics)
24 |
25 |
26 | return (
27 |
28 | {metrics && metrics.map((test, index) => (
29 |
30 | AssertionError: {test.fullName}
31 |
32 |
33 |
ID: {errorsDetails[0].id}
34 |
Failed: {test.failed} out of {test.totalRuns} runs
35 |
Passed: {test.passed} out of {test.totalRuns} runs
36 |
file: {errorsDetails[0].filePath}
37 |
38 |
39 |
40 | ))}
41 |
42 | )
43 | }
44 |
45 | export default ErrorsDetails;
46 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/line/LineChart.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | import React, {useEffect, useState} from 'react';
4 | import {ResponsiveLine} from '@nivo/line';
5 | import {lineChartParser} from '../../../utilities/lineChartParser';
6 |
7 | const LineChart: React.FC = ({results}) => {
8 | const [lineChartData, setLineChartData] = useState([]);
9 |
10 |
11 | useEffect(() => {
12 | const chartData = lineChartParser(results);
13 | // console.log('Parsed Chart Data:', chartData);
14 | if (Array.isArray(chartData)) setLineChartData(chartData);
15 | }, [results]);
16 |
17 | return (
18 |
77 | );
78 | };
79 |
80 | export default LineChart;
81 |
82 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/components/pie/PieChart.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | import React = require('react');
4 | import { useState, useEffect } from 'react';
5 | import {ResponsivePie} from '@nivo/pie';
6 | import {flakyDataParser} from '../../../utilities/flakyDataParser'
7 |
8 |
9 |
10 | const PieChart: React.FC = ({results}) => {
11 | const [piechartResults, setPiechartResults] = useState([]);
12 | const [flakyCount, setFlakyCount] = useState([]);
13 |
14 |
15 | useEffect(() => {
16 | const chartData = flakyDataParser(results);
17 | const latestRun = chartData[chartData.length - 1];//selects latest run
18 | const PieResults = [];
19 | let flakyTests = [];
20 | // console.log('Parsed PIE Chart Data: ---->', latestRun);
21 | for (let key in latestRun) {
22 | const data = {};
23 | if (key === 'alwaysPass') {
24 | data.id = 'passed';
25 | data.label = 'passed';
26 | data.value = latestRun[key];
27 | data.color = 'hsl(134, 61%, 41%)'
28 | PieResults.push(data);
29 | } else if (key === 'alwaysFail') {
30 | data.id = 'failed';
31 | data.label = 'failed';
32 | data.value = latestRun[key];
33 | data.color = 'hsl(354, 87%, 56%)'
34 | PieResults.push(data);
35 | } else if (key === 'skipped') {
36 | data.id = 'skipped';
37 | data.label = 'skipped';
38 | data.value = latestRun[key];
39 | data.color = 'hsl(0, 0%, 71%)'
40 | PieResults.push(data);
41 | } else if (key === 'flaky') {
42 | data.flakyTestsNum = latestRun[key]
43 | flakyTests.push(data)
44 | } else {
45 | data.totalTests = latestRun[key]
46 | flakyTests.push(data)
47 | }
48 | }
49 | if (Array.isArray(chartData)) {
50 | setPiechartResults(PieResults);
51 | setFlakyCount(flakyTests)
52 | }
53 | }, [results]);
54 |
55 | return (
56 | <>
57 | data.color}
60 | margin={{top: 40, right: 80, bottom: 80, left: 80}}
61 | innerRadius={0.5}
62 | padAngle={0.7}
63 | cornerRadius={3}
64 | activeOuterRadiusOffset={8}
65 | borderWidth={1}
66 | borderColor={{
67 | from: 'color',
68 | modifiers: [['darker', 0.2]],
69 | }}
70 | arcLinkLabelsSkipAngle={10}
71 | arcLinkLabelsTextColor="#333333"
72 | arcLinkLabelsThickness={2}
73 | arcLinkLabelsColor={{from: 'color'}}
74 | arcLabelsSkipAngle={10}
75 | arcLabelsTextColor={{
76 | from: 'color',
77 | modifiers: [['darker', 2]],
78 | }}
79 | defs={[
80 | {
81 | id: 'dots',
82 | type: 'patternDots',
83 | background: 'inherit',
84 | color: 'rgba(255, 255, 255, 0.3)',
85 | size: 4,
86 | padding: 1,
87 | stagger: true,
88 | },
89 | {
90 | id: 'lines',
91 | type: 'patternLines',
92 | background: 'inherit',
93 | color: 'rgba(255, 255, 255, 0.3)',
94 | rotation: -45,
95 | lineWidth: 6,
96 | spacing: 10,
97 | },
98 | ]}
99 | fill={[
100 | {
101 | match: {
102 | id: 'ruby',
103 | },
104 | id: 'dots',
105 | },
106 | {
107 | match: {
108 | id: 'c',
109 | },
110 | id: 'dots',
111 | },
112 | {
113 | match: {
114 | id: 'go',
115 | },
116 | id: 'dots',
117 | },
118 | {
119 | match: {
120 | id: 'python',
121 | },
122 | id: 'dots',
123 | },
124 | {
125 | match: {
126 | id: 'scala',
127 | },
128 | id: 'lines',
129 | },
130 | {
131 | match: {
132 | id: 'lisp',
133 | },
134 | id: 'lines',
135 | },
136 | {
137 | match: {
138 | id: 'elixir',
139 | },
140 | id: 'lines',
141 | },
142 | {
143 | match: {
144 | id: 'javascript',
145 | },
146 | id: 'lines',
147 | },
148 | ]}
149 | legends={[
150 | {
151 | anchor: 'bottom',
152 | direction: 'row',
153 | justify: false,
154 | translateX: 0,
155 | translateY: 56,
156 | itemsSpacing: 0,
157 | itemWidth: 100,
158 | itemHeight: 18,
159 | itemTextColor: '#999',
160 | itemDirection: 'left-to-right',
161 | itemOpacity: 1,
162 | symbolSize: 18,
163 | symbolShape: 'circle',
164 | effects: [
165 | {
166 | on: 'hover',
167 | style: {
168 | itemTextColor: '#000',
169 | },
170 | },
171 | ],
172 | },
173 | ]}
174 | />
175 | {flakyCount[0] &&
176 |
177 |
{flakyCount[1].flakyTestsNum}/{flakyCount[2].totalTests} are flaky
178 |
179 | }
180 | >
181 | );
182 | };
183 |
184 | export default PieChart;
185 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/contexts/ResultContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, createContext } from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import { api } from '../../services/index';
4 |
5 | // Create the context
6 | export const ResultsContext = createContext([]);
7 |
8 | const ResultsProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
9 | const { userId } = useParams();
10 | const [results, setResults] = useState([]);
11 |
12 | useEffect(() => {
13 | const getResults = async () => {
14 | try {
15 | const response = await api.get(`/userDash/${userId}`);
16 | const resultsArray = response.data;
17 |
18 | // Add a yyyy-mm-dd date to each result
19 | for (const result of resultsArray) {
20 | const ts = result.created_at;
21 | result.date = ts.slice(0, ts.indexOf('T'));
22 | }
23 | // console.log('RESULTS USERDASH CONTEXT --->', resultsArray);
24 | setResults(resultsArray);
25 | } catch (error) {
26 | console.log('Error getting results: ', error);
27 | }
28 | };
29 |
30 | getResults();
31 | }, [userId]);
32 |
33 | return (
34 |
35 | {children}
36 |
37 | );
38 | };
39 |
40 | export default ResultsProvider;
41 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/pages/codeCoverage/CodeCoverage.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { useLocation } from 'react-router-dom';
3 | import MenuSidebar from "../../components/Sidebar";
4 | import { useContext } from "react";
5 | import {ResultsContext} from '../../contexts/ResultContext'
6 |
7 |
8 |
9 | const CodeCoverage: React.FC = () => {
10 | const results = useContext(ResultsContext); // Access context value correctly
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
Coverage feature in development
19 | Check back soon!
20 |
21 |
22 | )
23 | }
24 |
25 | export default CodeCoverage;
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/pages/flakyTests/FlakyTests.tsx:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import MenuSidebar from "../../components/Sidebar";
3 | import { useContext, useState, useEffect } from "react";
4 | import { ResultsContext } from '../../contexts/ResultContext';
5 | import PieChart from "../../components/pie/PieChart";
6 | import BarChart from "../../components/bar/BarChart";
7 | import {barChartParser} from '../../../utilities/barchartDataParser'
8 |
9 | const FlakyTests: React.FC = () => {
10 | const results = useContext(ResultsContext);
11 | const [barChartData, setBarChartData] = useState([]);
12 |
13 | useEffect(() => {
14 | const chartData = barChartParser(results);
15 | const latestRun = chartData.slice(-3); //Display the last 20 runs
16 | // console.log('Parsed BAR Chart Data:', latestRun);
17 | if (Array.isArray(latestRun)) setBarChartData(latestRun);
18 | }, [results]);
19 |
20 |
21 | console.log('barChartData', barChartData)
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
In-Depth Details about Flaky Tests Feature
29 | Check back soon!
30 |
31 |
32 | );
33 | }
34 |
35 | export default FlakyTests;
36 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/userDash/pages/history/History.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import { useLocation } from 'react-router-dom';
3 | import MenuSidebar from "../../components/Sidebar";
4 | import { useContext } from "react";
5 | import {ResultsContext} from '../../contexts/ResultContext'
6 |
7 |
8 |
9 | const History: React.FC = () => {
10 | const results = useContext(ResultsContext); // Access context value correctly
11 |
12 | console.log('history component --->', results)
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
History feature in development
21 | Check back soon!
22 | {/* {results} */}
23 |
24 |
25 | )
26 | }
27 |
28 | export default History;
--------------------------------------------------------------------------------
/flake-guard-app/src/client/utilities/barchartDataParser.ts:
--------------------------------------------------------------------------------
1 | export interface FG {
2 | created_at: string;
3 | date: string;
4 | id: string;
5 | user_id: string;
6 | results: Result;
7 | }
8 |
9 | interface Result {
10 | metrics: Array;
11 | verbose: Array;
12 | }
13 |
14 | interface Test {
15 | failed: number;
16 | fullName: string;
17 | passed: number;
18 | skipped: number;
19 | totalRuns: number;
20 | }
21 |
22 | interface DataPoint {
23 | id: string | null;
24 | passed: number | null;
25 | passedColor: string | null;
26 | failed: number | null;
27 | failedColor: string | null;
28 | skipped: number | null;
29 | skippedColor: string | null;
30 | }
31 |
32 | const barChartParser = (userResults: Array) => {
33 | // Input: The whole user results array
34 | const output: Array = [];
35 | userResults.forEach((fg: FG) => {
36 | const dataPoint: DataPoint = {
37 | id: null,
38 | passed: null,
39 | passedColor: null,
40 | failed: null,
41 | failedColor: null,
42 | skipped: null,
43 | skippedColor: null,
44 | };
45 | dataPoint.id = fg.id;
46 |
47 | let passed: number = 0;
48 | let failed: number = 0;
49 | let skipped: number = 0;
50 |
51 | if (Array.isArray(fg.results.metrics)) {
52 | fg.results.metrics.forEach((test: Test) => {
53 | if (dataPoint.id === fg.id) {
54 | passed += test.passed;
55 | failed += test.failed;
56 | skipped += test.skipped;
57 | }
58 | dataPoint.passed = passed;
59 | dataPoint.passedColor = 'hsl(134, 61%, 41%)';
60 | dataPoint.failed = failed;
61 | dataPoint.failedColor = 'hsl(354, 87%, 56%)';
62 | dataPoint.skipped = skipped;
63 | dataPoint.skippedColor = 'hsl(0, 0%, 71%)';
64 | });
65 | }
66 | output.push(dataPoint);
67 | });
68 | // console.log('output from barchart ', output)
69 | return output;
70 | };
71 |
72 | export {barChartParser};
--------------------------------------------------------------------------------
/flake-guard-app/src/client/utilities/displayErrorsDataParser.ts:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | export interface FG {
4 | created_at: string;
5 | date: string;
6 | id: string;
7 | user_id: string;
8 | results: Result;
9 | }
10 |
11 | interface Result {
12 | metrics: Array;
13 | verbose: Array;
14 | }
15 |
16 | interface Test {
17 | failed: number;
18 | fullName: string;
19 | passed: number;
20 | skipped: number;
21 | totalRuns: number;
22 | }
23 |
24 | interface DataPoint {
25 | id: string | null;
26 | message: string | null;
27 | filePath: string | null;
28 | details: Array | null;
29 | durations: Array | null
30 | }
31 |
32 | const displayErrorsDataParser = (userResults: Array) => {
33 |
34 | // Input: The whole user results array
35 | const output: Array = [];
36 | userResults.forEach((fg: FG) => {
37 | const dataPoint: DataPoint = {
38 | id: null,
39 | message: null,
40 | filePath: null,
41 | details: null,
42 | durations: null,
43 | };
44 |
45 | dataPoint.id = fg.id
46 | dataPoint.details = fg.results.metrics
47 |
48 | const runDurations = []
49 |
50 | if (Array.isArray(fg.results.verbose)) {
51 | fg.results.verbose.forEach((run: any, index) => {
52 | const startTime = run.startTime;
53 | const endTime = run.testResults[0].endTime
54 | const runDuration = endTime - startTime
55 |
56 | runDurations.push(runDuration)
57 | })
58 | }
59 | dataPoint.durations = runDurations
60 |
61 | if (Array.isArray(fg.results.metrics)) {
62 | fg.results.metrics.forEach((test: Test) => {
63 | const message = fg.results.verbose[0].testResults[0].message
64 | const filePath = fg.results.verbose[0].testResults[0].name
65 |
66 | dataPoint.message = message;
67 | dataPoint.filePath = filePath;
68 | });
69 | }
70 |
71 | output.push(dataPoint);
72 | });
73 | // console.log('output from utilities ->', output)
74 | return output;
75 | };
76 |
77 | export {displayErrorsDataParser};
--------------------------------------------------------------------------------
/flake-guard-app/src/client/utilities/flakyDataParser.ts:
--------------------------------------------------------------------------------
1 | export interface FG {
2 | created_at: string;
3 | date: string;
4 | id: string;
5 | user_id: string;
6 | results: Result;
7 | }
8 |
9 | interface Result {
10 | metrics: Array;
11 | verbose: Array;
12 | }
13 |
14 | interface Test {
15 | failed: number;
16 | fullName: string;
17 | passed: number;
18 | skipped: number;
19 | totalRuns: number;
20 | }
21 |
22 | interface FlakyData {
23 | date: string | null;
24 | flaky: number | null;
25 | alwaysFail: number | null;
26 | alwaysPass: number | null;
27 | skipped: number | null;
28 | totalTests: number | null;
29 | }
30 |
31 | const flakyDataParser = (userResults: Array): Array => {
32 | const output: Array = [];
33 |
34 | userResults.forEach((fg: FG) => {
35 | const data: FlakyData = {
36 | date: null,
37 | flaky: null,
38 | alwaysFail: null,
39 | alwaysPass: null,
40 | skipped: null,
41 | totalTests: null,
42 | };
43 |
44 | let flaky = 0;
45 | let alwaysFail = 0;
46 | let alwaysPass = 0;
47 | let skipped = 0;
48 | let totalTests = 0;
49 |
50 | if (Array.isArray(fg.results.metrics)) {
51 | fg.results.metrics.forEach((test: Test) => {
52 | // console.log('teeest ->>>>', test)
53 | if (test.skipped === test.totalRuns) {
54 | skipped += 1;
55 | } else if (test.passed === test.totalRuns) {
56 | alwaysPass += 1;
57 | } else if (test.failed === test.totalRuns) {
58 | alwaysFail += 1;
59 | } else {
60 | flaky += 1;
61 | }
62 | totalTests +=
63 | (test.skipped + test.failed + test.passed) / test.totalRuns;
64 | });
65 | data.date = fg.date;
66 | data.flaky = flaky;
67 | data.alwaysFail = alwaysFail;
68 | data.alwaysPass = alwaysPass;
69 | data.skipped = skipped;
70 | data.totalTests = totalTests;
71 |
72 |
73 | output.push(data);
74 | }
75 | });
76 |
77 | return output;
78 | };
79 |
80 | export {flakyDataParser};
81 |
--------------------------------------------------------------------------------
/flake-guard-app/src/client/utilities/lineChartParser.ts:
--------------------------------------------------------------------------------
1 | export interface FG {
2 | created_at: string;
3 | date: string;
4 | id: string;
5 | user_id: string;
6 | results: Result;
7 | }
8 |
9 | interface Result {
10 | metrics: Array;
11 | verbose: Array;
12 | }
13 |
14 | interface Test {
15 | failed: number;
16 | fullName: string;
17 | passed: number;
18 | skipped: number;
19 | totalRuns: number;
20 | }
21 |
22 | interface DataPoint {
23 | x: string | null;
24 | y: number | null;
25 | }
26 |
27 | const lineChartParser = (userResults: Array) => {
28 | // Input: The whole user results array
29 | const output: Array = [];
30 | userResults.forEach((fg: FG) => {
31 | const dataPoint: DataPoint = {x: null, y: null};
32 | dataPoint.x = fg.created_at;
33 | let flaky: number = 0;
34 | let totalTests: number = 0;
35 | if (Array.isArray(fg.results.metrics)) {
36 | fg.results.metrics.forEach((test: Test) => {
37 | if (
38 | test.totalRuns - test.passed !== test.skipped &&
39 | test.totalRuns - test.failed !== test.skipped
40 | ) {
41 | flaky += 1;
42 | }
43 | totalTests += 1;
44 | });
45 | }
46 | const flakePercentage: number = (flaky / totalTests) * 100;
47 | dataPoint.y = flakePercentage;
48 | if (typeof flakePercentage === 'number' && !isNaN(flakePercentage))
49 | output.push(dataPoint);
50 | });
51 | return output;
52 | };
53 |
54 | export {lineChartParser};
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/cacheController.test.js:
--------------------------------------------------------------------------------
1 | import cacheController from '../../server/controllers/cacheController';
2 | import CustomError from '../../server/errors/CustomError';
3 | import { setCache, getCache, deleteCache } from '../../server/tempCache';
4 |
5 | jest.mock('../../server/tempCache');
6 |
7 | describe('cacheController', () => {
8 | let req;
9 | let res;
10 | let next;
11 |
12 | beforeEach(() => {
13 | req = {
14 | params: {},
15 | };
16 | res = {
17 | locals: {},
18 | };
19 | next = jest.fn();
20 | });
21 |
22 | describe('cacheTempResults', () => {
23 | it('should cache results and call next', () => {
24 | res.locals.randomString = 'testString';
25 | res.locals.metrics = 'testMetrics';
26 | res.locals.verbose = 'testVerbose';
27 |
28 | cacheController.cacheTempResults(req, res, next);
29 |
30 | expect(setCache).toHaveBeenCalledWith('testString', {
31 | metrics: 'testMetrics',
32 | verbose: 'testVerbose',
33 | });
34 | expect(next).toHaveBeenCalled();
35 | });
36 |
37 | it('should call next with a CustomError on failure', () => {
38 | setCache.mockImplementationOnce(() => {
39 | throw new Error('Test error');
40 | });
41 |
42 | cacheController.cacheTempResults(req, res, next);
43 |
44 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
45 | });
46 | });
47 |
48 | describe('retrieveResults', () => {
49 | it('should retrieve cached results and call next', () => {
50 | req.params.id = 'testId';
51 | getCache.mockReturnValueOnce('testResults').mockReturnValueOnce('testResults');
52 |
53 | cacheController.retrieveResults(req, res, next);
54 |
55 | expect(getCache).toHaveBeenCalledWith('testId');
56 | expect(res.locals.tempCachedResults).toBe('testResults');
57 | expect(next).toHaveBeenCalled();
58 | });
59 |
60 | it('should log an error and call next if no data is found in cache', () => {
61 | console.error = jest.fn();
62 | req.params.id = 'testId';
63 | getCache.mockReturnValueOnce(null);
64 |
65 | cacheController.retrieveResults(req, res, next);
66 |
67 | expect(getCache).toHaveBeenCalledWith('testId');
68 | expect(console.error).toHaveBeenCalledWith('no data with id testId in cache');
69 | expect(next).toHaveBeenCalled();
70 | });
71 |
72 | it('should call next with a CustomError on failure', () => {
73 | getCache.mockImplementationOnce(() => {
74 | throw new Error('Test error');
75 | });
76 |
77 | cacheController.retrieveResults(req, res, next);
78 |
79 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
80 | });
81 | });
82 |
83 | describe('evictViewedResults', () => {
84 | it('should evict cached results and call next', () => {
85 | req.params.id = 'testId';
86 |
87 | cacheController.evictViewedResults(req, res, next);
88 |
89 | expect(deleteCache).toHaveBeenCalledWith('testId');
90 | expect(next).toHaveBeenCalled();
91 | });
92 |
93 | it('should call next with a CustomError on failure', () => {
94 | deleteCache.mockImplementationOnce(() => {
95 | throw new Error('Test error');
96 | });
97 |
98 | cacheController.evictViewedResults(req, res, next);
99 |
100 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
101 | });
102 | });
103 | });
104 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/cacheController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response, NextFunction} from 'express';
2 | import {setCache, getCache, deleteCache} from '../tempCache';
3 | import CustomError from '../errors/CustomError';
4 |
5 | interface CacheController {
6 | cacheTempResults: (req: Request, res: Response, next: NextFunction) => void;
7 | retrieveResults: (req: Request, res: Response, next: NextFunction) => void;
8 | evictViewedResults: (req: Request, res: Response, next: NextFunction) => void;
9 | }
10 |
11 | const cacheController: CacheController = {
12 | cacheTempResults: (req: Request, res: Response, next: NextFunction) => {
13 | try {
14 | setCache(res.locals.randomString, {
15 | metrics: res.locals.metrics,
16 | verbose: res.locals.verbose,
17 | });
18 | next();
19 | } catch (error: unknown) {
20 | const customError = new CustomError(
21 | 'Failed to cache results',
22 | 500,
23 | 'Error in cacheTempResults',
24 | error
25 | );
26 | return next(customError);
27 | }
28 | },
29 |
30 | retrieveResults: (req: Request, res: Response, next: NextFunction) => {
31 | try {
32 | const {id} = req.params;
33 | if (getCache(id)) {
34 | res.locals.tempCachedResults = getCache(id);
35 | } else {
36 | console.error(`no data with id ${id} in cache`);
37 | }
38 | next();
39 | } catch (error) {
40 | const customError = new CustomError(
41 | 'Failed to retrieve cached results',
42 | 500,
43 | 'Error in retrieveResults',
44 | error
45 | );
46 | return next(customError);
47 | }
48 | },
49 |
50 | evictViewedResults: (req: Request, res: Response, next: NextFunction) => {
51 | try {
52 | const {id} = req.params;
53 | deleteCache(id);
54 | return next();
55 | } catch (error) {
56 | const customError = new CustomError(
57 | 'Failed to evict cached results',
58 | 500,
59 | 'Error in evictViewedResults',
60 | error
61 | );
62 | return next(customError);
63 | }
64 | },
65 | };
66 |
67 | export default cacheController;
68 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/dbController.test.js:
--------------------------------------------------------------------------------
1 | import sql from '../../server/db/db';
2 | import dbController, { DBController } from '../../server/controllers/dbController';
3 | import CustomError from '../../server/errors/CustomError';
4 |
5 | jest.mock('../../server/db/db', () => ({
6 | __esModule: true,
7 | default: jest.fn(),
8 | }));
9 |
10 | describe('dbController', () => {
11 | let req;
12 | let res;
13 | let next;
14 |
15 | beforeEach(() => {
16 | req = { params: {}, body: {} };
17 | res = { locals: {} };
18 | next = jest.fn();
19 | sql.mockClear();
20 | });
21 |
22 | describe('retrieveResults', () => {
23 | it('should retrieve results and call next', async () => {
24 | const userId = 'testUserId';
25 | const mockResults = [{ id: 1, score: 100 }, { id: 2, score: 95 }];
26 | sql.mockResolvedValueOnce(mockResults);
27 |
28 | req.params.userId = userId;
29 | await dbController.retrieveResults(req, res, next);
30 |
31 | expect(res.locals.results).toEqual(mockResults);
32 | expect(next).toHaveBeenCalled();
33 | });
34 |
35 | it('should call next with a CustomError on database query failure', async () => {
36 | const userId = 'testUserId';
37 | const mockError = new Error('Database query error');
38 | sql.mockRejectedValueOnce(mockError);
39 |
40 | req.params.userId = userId;
41 | await dbController.retrieveResults(req, res, next);
42 |
43 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
44 | expect(next.mock.calls[0][0].status).toBe(500);
45 | expect(next.mock.calls[0][0].log).toBe('ERROR---> Database query in retrieveResults failed: \n');
46 | });
47 | });
48 |
49 | describe('saveResults', () => {
50 | it('should save results to database and call next', async () => {
51 | const userId = 'testUserId';
52 | const mockResults = [{ id: 1, score: 100 }, { id: 2, score: 95 }];
53 | req.params.userId = userId;
54 | req.body.results = mockResults;
55 |
56 | sql.mockResolvedValueOnce({});
57 |
58 | await dbController.saveResults(req, res, next);
59 |
60 | expect(next).toHaveBeenCalled();
61 | });
62 |
63 | it('should call next with a CustomError on database query failure', async () => {
64 | const userId = 'testUserId';
65 | const mockResults = [{ id: 1, score: 100 }, { id: 2, score: 95 }];
66 | const mockError = new Error('Database query error');
67 | sql.mockRejectedValueOnce(mockError);
68 |
69 | req.params.userId = userId;
70 | req.body.results = mockResults;
71 |
72 | await dbController.saveResults(req, res, next);
73 |
74 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
75 | expect(next.mock.calls[0][0].status).toBe(500);
76 | expect(next.mock.calls[0][0].log).toBe('ERROR---> Database query in saveResults failed: \n');
77 | });
78 | });
79 | });
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/dbController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response, NextFunction} from 'express';
2 | import sql from '../db/db';
3 | import CustomError from '../errors/CustomError';
4 |
5 | interface DBController {
6 | retrieveResults: (req: Request, res: Response, next: NextFunction) => void;
7 | saveResults: (req: Request, res: Response, next: NextFunction) => void;
8 | }
9 |
10 | const dbController: DBController = {
11 | retrieveResults: async (req: Request, res: Response, next: NextFunction) => {
12 | try {
13 | const userId = req.params.userId;
14 | const results = await sql`SELECT * FROM results WHERE user_id=${userId} ORDER BY created_at`;
15 | res.locals.results = results;
16 | return next();
17 | } catch (error) {
18 | const customError = new CustomError(
19 | 'Failed to retrieve results',
20 | 500,
21 | 'Database query in retrieveResults failed',
22 | error
23 | );
24 | return next(customError);
25 | }
26 | },
27 |
28 | saveResults: async (req: Request, res: Response, next: NextFunction) => {
29 | try {
30 | const userId = req.params.userId;
31 | const results = req.body.results;
32 | await sql`INSERT INTO results(user_id, results) VALUES (${userId}, ${results})`;
33 | return next();
34 | } catch (error) {
35 | const customError = new CustomError(
36 | 'Failed to save results to database',
37 | 500,
38 | 'Database query in saveResults failed',
39 | error
40 | );
41 | return next(customError);
42 | }
43 | },
44 | };
45 |
46 | export default dbController;
47 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/npmController.test.js:
--------------------------------------------------------------------------------
1 | import npmController from '../../server/controllers/npmController';
2 | import CustomError from '../../server/errors/CustomError';
3 |
4 | describe('npmController', () => {
5 | let req;
6 | let res;
7 | let next;
8 |
9 | beforeEach(() => {
10 | req = {
11 | body: {
12 | runTimes: 5,
13 | simple: {
14 | 'testPackage1': { passed: 10, failed: 2, skipped: 3 },
15 | 'testPackage2': { passed: 15, failed: 1, skipped: 0 },
16 | },
17 | verbose: true,
18 | },
19 | };
20 | res = {
21 | locals: {},
22 | };
23 | next = jest.fn();
24 | });
25 |
26 | describe('npmMetrics', () => {
27 | it('should parse metrics and call next', async () => {
28 | await npmController.npmMetrics(req, res, next);
29 |
30 | expect(res.locals.metrics).toEqual([
31 | {
32 | fullName: 'testPackage1',
33 | passed: 10,
34 | failed: 2,
35 | totalRuns: 5,
36 | skipped: 3,
37 | },
38 | {
39 | fullName: 'testPackage2',
40 | passed: 15,
41 | failed: 1,
42 | totalRuns: 5,
43 | skipped: 0,
44 | },
45 | ]);
46 | expect(res.locals.verbose).toBe(true);
47 | expect(next).toHaveBeenCalled();
48 | });
49 |
50 | it('should call next with a CustomError on failure', async () => {
51 | req.body = null;
52 |
53 | await npmController.npmMetrics(req, res, next);
54 |
55 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
56 | });
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/npmController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response, NextFunction} from 'express';
2 | import CustomError from '../errors/CustomError';
3 | // import CustomError from '../errors/CustomError';
4 | interface NPMController {
5 | npmMetrics: (req: Request, res: Response, next: NextFunction) => void;
6 | }
7 |
8 | const npmController: NPMController = {
9 | // Receives and parses the results from the user via NPM package
10 | async npmMetrics(
11 | req: Request,
12 | res: Response,
13 | next: NextFunction
14 | ): Promise {
15 | try {
16 | if (!req.body) throw new Error('No data found in req.body');
17 | const {runTimes, simple, verbose} = req.body;
18 | // Parse the simple results into an array of results objects for front-end dashboard
19 | const metrics = [];
20 | for (const key in simple) {
21 | const fullName = key;
22 | const passed = simple[key].passed;
23 | const failed = simple[key].failed;
24 | const skipped = simple[key].skipped;
25 |
26 | metrics.push({
27 | fullName: fullName,
28 | passed: passed,
29 | failed: failed,
30 | totalRuns: runTimes,
31 | skipped: skipped,
32 | });
33 | }
34 |
35 | res.locals.metrics = metrics;
36 | res.locals.verbose = verbose;
37 |
38 | return next();
39 | } catch (error) {
40 | const customError = new CustomError(
41 | 'Failed to parse NPM metrics',
42 | 500,
43 | 'Error in npmMetrics',
44 | error
45 | );
46 | return next(customError);
47 | }
48 | },
49 | };
50 |
51 | export default npmController;
52 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/tempCache.test.js:
--------------------------------------------------------------------------------
1 | import { setCache, getCache, deleteCache, clearCache, tempCache } from '../../server/tempCache';
2 |
3 | describe('tempCache module', () => {
4 | afterEach(() => {
5 | clearCache();
6 | });
7 |
8 | describe('setCache', () => {
9 | it('should set a value in the cache', () => {
10 | setCache('testKey', { data: 'testData' });
11 | expect(tempCache.get('testKey')).toEqual({ data: 'testData' });
12 | });
13 | });
14 |
15 | describe('getCache', () => {
16 | it('should retrieve a value from the cache', () => {
17 | tempCache.set('testKey', { data: 'testData' });
18 | expect(getCache('testKey')).toEqual({ data: 'testData' });
19 | });
20 |
21 | it('should return undefined if the key does not exist', () => {
22 | expect(getCache('nonExistentKey')).toBeUndefined();
23 | });
24 | });
25 |
26 | describe('deleteCache', () => {
27 | it('should delete a value from the cache', () => {
28 | tempCache.set('testKey', { data: 'testData' });
29 | deleteCache('testKey');
30 | expect(tempCache.has('testKey')).toBe(false);
31 | });
32 | });
33 |
34 | describe('clearCache', () => {
35 | it('should clear the cache', () => {
36 | tempCache.set('testKey1', { data: 'testData1' });
37 | tempCache.set('testKey2', { data: 'testData2' });
38 | clearCache();
39 | expect(tempCache.size).toBe(0);
40 | });
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/urlController.test.js:
--------------------------------------------------------------------------------
1 | import urlController from '../../server/controllers/urlController';
2 | import CustomError from '../../server/errors/CustomError';
3 |
4 | describe('urlController', () => {
5 | let req;
6 | let res;
7 | let next;
8 |
9 | beforeEach(() => {
10 | req = {};
11 | res = {
12 | locals: {},
13 | };
14 | next = jest.fn();
15 | });
16 |
17 | describe('generateRandomString method', () => {
18 | it('should generate a random string, store it in res.locals, and call next', () => {
19 | urlController.generateRandomString(req, res, next);
20 | expect(res.locals.randomString).toHaveLength(10);
21 | expect(next).toHaveBeenCalled();
22 | });
23 | it('should call next with a CustomError on failure', () =>{
24 | // Mock Math.random to throw an error
25 | const originalRandom = Math.random;
26 | Math.random = jest.fn(() => {
27 | throw new Error('Test error');
28 | });
29 | urlController.generateRandomString(req, res, next);
30 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
31 | // Restore original Math.random
32 | Math.random = originalRandom;
33 | });
34 | });
35 |
36 | describe('buildCustomUrl method', () => {
37 | it('should generate a url and call next', () => {
38 | res.locals.randomString = 'abcdefghij';
39 | urlController.buildCustomUrl(req, res, next);
40 | expect(res.locals.tempUrl).toBe('https://flakeguard.com/npm/abcdefghij')
41 | expect(next).toHaveBeenCalled();
42 | });
43 | it('should call next with a CustomError on failure', () => {
44 | // cause an error
45 | res.locals = null;
46 | urlController.buildCustomUrl(req, res, next);
47 | expect(next).toHaveBeenCalledWith(expect.any(CustomError));
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/controllers/urlController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response, NextFunction} from 'express';
2 | import CustomError from '../errors/CustomError';
3 |
4 | interface UrlController {
5 | generateRandomString: (
6 | req: Request,
7 | res: Response,
8 | next: NextFunction
9 | ) => void;
10 | buildCustomUrl: (req: Request, res: Response, next: NextFunction) => void;
11 | }
12 |
13 | const urlController: UrlController = {
14 | generateRandomString: (req: Request, res: Response, next: NextFunction) => {
15 | try {
16 | const characters =
17 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
18 | let randomString = '';
19 | const charactersLength = characters.length;
20 | for (let i = 0; i < 10; i++) {
21 | randomString += characters.charAt(
22 | Math.floor(Math.random() * charactersLength)
23 | );
24 | }
25 | res.locals.randomString = randomString;
26 | next();
27 | } catch (error) {
28 | const customError = new CustomError(
29 | 'Failed generate random string',
30 | 500,
31 | 'Error in generateRandomString',
32 | error
33 | );
34 | return next(customError);
35 | }
36 | },
37 |
38 | buildCustomUrl: (req: Request, res: Response, next: NextFunction) => {
39 | try {
40 | const tempUrl: string = `https://flakeguard.com/npm/${res.locals.randomString}`;
41 | res.locals.tempUrl = tempUrl;
42 | next();
43 | } catch (error) {
44 | const customError = new CustomError(
45 | 'Failed to build URL',
46 | 500,
47 | 'Error in buildCustomURL',
48 | error
49 | );
50 | return next(customError);
51 | }
52 | },
53 | };
54 |
55 | export default urlController;
56 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/db/db.ts:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const postgres = require('postgres');
3 |
4 | const connectionString = process.env.DATABASE_URL;
5 | const sql = postgres(connectionString);
6 |
7 | export default sql;
8 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/errors/CustomError.ts:
--------------------------------------------------------------------------------
1 | class CustomError {
2 | constructor(
3 | public message: string,
4 | public status: number,
5 | public log: String,
6 | public error: unknown
7 | ) {
8 | this.message = message;
9 | this.status = status;
10 | this.log = `ERROR---> ${log}: \n`;
11 | this.error = error;
12 | }
13 | }
14 |
15 | export default CustomError;
16 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/errors/errorHandler.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response, NextFunction} from 'express';
2 | import CustomError from './CustomError';
3 |
4 | // Error handler middleware
5 | export const errorHandler = (
6 | err: Error,
7 | req: Request,
8 | res: Response,
9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
10 | next: NextFunction
11 | ) => {
12 | if (err instanceof CustomError) {
13 | console.error(err.log, err.error);
14 | return res.status(err.status).json({error: err.message});
15 | } else {
16 | console.error('Unknown middleware error encountered', err);
17 | return res.status(500).json({error: 'Server error occurred'});
18 | }
19 | };
20 |
21 | export default errorHandler;
22 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/errors/errors.ts:
--------------------------------------------------------------------------------
1 | // base error class
2 | export class BaseError extends Error {
3 | status: number;
4 | isOperational: boolean;
5 | constructor(message: string, status: number, isOperational = true) {
6 | super(message);
7 | this.status = status;
8 | this.isOperational = isOperational;
9 | Object.setPrototypeOf(this, BaseError.prototype);
10 | }
11 | }
12 |
13 | // 404 error class
14 | export class NotFoundError extends BaseError {
15 | constructor(message: string) {
16 | super(message, 404);
17 | Object.setPrototypeOf(this, NotFoundError.prototype);
18 | }
19 | }
20 |
21 | // validation error class
22 | export class ValidationError extends BaseError {
23 | errorData: Record[];
24 | constructor(data: Record[]) {
25 | super('Validation Error', 400);
26 | this.errorData = data;
27 | Object.setPrototypeOf(this, ValidationError.prototype);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/routes/resultsRouter.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import npmController from '../controllers/npmController';
3 | import urlController from '../controllers/urlController';
4 | import cacheController from '../controllers/cacheController';
5 | import {Request, Response} from 'express';
6 |
7 | const router = express.Router();
8 |
9 | router.post(
10 | '/',
11 | npmController.npmMetrics,
12 | urlController.generateRandomString,
13 | cacheController.cacheTempResults,
14 | urlController.buildCustomUrl,
15 | (req: Request, res: Response) => {
16 | return res.status(200).json(res.locals.tempUrl);
17 | }
18 | );
19 |
20 | router.get(
21 | '/:id',
22 | cacheController.retrieveResults,
23 | (req: Request, res: Response) => {
24 | return res.status(200).json(res.locals.tempCachedResults);
25 | }
26 | );
27 |
28 | export default router;
29 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/routes/tempDashRouter.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import {Request, Response} from 'express';
3 | import cacheController from '../controllers/cacheController';
4 |
5 | const router = express.Router();
6 |
7 | router.get(
8 | '/:id',
9 | cacheController.retrieveResults,
10 | (req: Request, res: Response) => {
11 | return res.status(200).json(res.locals.tempCachedResults.metrics);
12 | }
13 | );
14 |
15 | router.get(
16 | '/verbose/:id',
17 | cacheController.retrieveResults,
18 | (req: Request, res: Response) => {
19 | return res.status(200).json(res.locals.tempCachedResults);
20 | }
21 | );
22 |
23 | router.delete(
24 | '/:id',
25 | cacheController.evictViewedResults,
26 | (req: Request, res: Response) => {
27 | return res.status(200).send();
28 | }
29 | );
30 |
31 | export default router;
32 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/routes/userDashRouter.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import {Request, Response} from 'express';
3 | import dbController from '../controllers/dbController';
4 |
5 | const router = express.Router();
6 |
7 | router.get(
8 | '/:userId',
9 | dbController.retrieveResults,
10 | (req: Request, res: Response) => {
11 | return res.status(200).json(res.locals.results);
12 | }
13 | );
14 |
15 | router.post(
16 | '/:userId',
17 | dbController.saveResults,
18 | (req: Request, res: Response) => {
19 | return res.status(200).json(res.locals.results);
20 | }
21 | );
22 |
23 | export default router;
24 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/server.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import path from 'path';
3 | import bodyParser from 'body-parser';
4 | import cors from 'cors';
5 | require('dotenv').config();
6 | import resultsRouter from './routes/resultsRouter';
7 | import userDashRouter from './routes/userDashRouter';
8 | import tempDashRouter from './routes/tempDashRouter';
9 | import {errorHandler} from './errors/errorHandler';
10 | import {Request, Response} from 'express';
11 |
12 | const app = express();
13 | const PORT = process.env.PORT || 3000;
14 |
15 | // Default middleware
16 | app.use(bodyParser.json({limit: '10mb'}));
17 | app.use(cors());
18 | app.use(express.static(path.resolve(__dirname, '../../dist')));
19 |
20 | // Endpoints
21 | app.use('/results', resultsRouter);
22 | app.use('/tempDash', tempDashRouter);
23 | app.use('/userDash', userDashRouter);
24 |
25 | // Catch all
26 | app.use('*', (req: Request, res: Response) => {
27 | res.sendFile(path.resolve(__dirname, '../../dist/index.html'));
28 | });
29 |
30 | // Global error handler
31 | app.use(errorHandler);
32 |
33 | app.listen(PORT, () => {
34 | console.log(` 🚀 Server running on port ${PORT}`);
35 | });
36 |
--------------------------------------------------------------------------------
/flake-guard-app/src/server/tempCache.ts:
--------------------------------------------------------------------------------
1 | const tempCache = new Map();
2 |
3 | export function setCache(key: string, value: object): void {
4 | tempCache.set(key, value);
5 | }
6 |
7 | export function getCache(key: string): object | undefined {
8 | return tempCache.get(key);
9 | }
10 |
11 | export function deleteCache(key: string): void {
12 | tempCache.delete(key);
13 | }
14 |
15 | export function clearCache(): void {
16 | tempCache.clear();
17 | }
18 |
19 | export {tempCache};
20 |
--------------------------------------------------------------------------------
/flake-guard-app/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/flake-guard-app/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli
2 |
3 | // choose either HTML webpack plugin OR use the index.html file we have within our code
4 | const path = require('path');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7 | const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
8 |
9 | const isProduction = process.env.NODE_ENV === 'production';
10 |
11 | const stylesHandler = MiniCssExtractPlugin.loader;
12 |
13 | const config = {
14 | entry: './src/client/index.tsx',
15 | output: {
16 | path: path.resolve(__dirname, 'dist'),
17 | filename: 'bundle.js', // Specify an output filename
18 | publicPath: '/', // Ensure correct public path
19 | },
20 | resolve: {
21 | extensions: ['.ts', '.js', 'tsx', 'jsx'],
22 | },
23 | devServer: {
24 | open: true,
25 | host: 'localhost',
26 | historyApiFallback: true,
27 | },
28 | plugins: [
29 | new HtmlWebpackPlugin({
30 | template: './src/client/index.html',
31 | favicon: './src/client/assets/condensed-logo.png',
32 | }),
33 |
34 | new MiniCssExtractPlugin(),
35 |
36 | // Add your plugins here
37 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/
38 | ],
39 | module: {
40 | rules: [
41 | {
42 | test: /\.(css)$/i,
43 | use: [stylesHandler, 'css-loader'],
44 | },
45 | {
46 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
47 | type: 'asset',
48 | },
49 | {
50 | test: /\.(ts|tsx)$/,
51 | exclude: /node_modules/,
52 | use: 'ts-loader',
53 | resolve: {
54 | extensions: ['.ts', '.tsx', '.js', '.json'],
55 | },
56 | },
57 | // Add your rules for custom modules here
58 | // Learn more about loaders from https://webpack.js.org/loaders/
59 | ],
60 | },
61 | };
62 |
63 | module.exports = () => {
64 | if (isProduction) {
65 | config.mode = 'production';
66 | config.plugins.push(new WorkboxWebpackPlugin.GenerateSW());
67 | } else {
68 | config.mode = 'development';
69 | }
70 | return config;
71 | };
72 |
--------------------------------------------------------------------------------
/flake-guard-npm/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | lerna-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # TypeScript v1 declaration files
47 | typings/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Microbundle cache
59 | .rpt2_cache/
60 | .rts2_cache_cjs/
61 | .rts2_cache_es/
62 | .rts2_cache_umd/
63 |
64 | # Optional REPL history
65 | .node_repl_history
66 |
67 | # Output of 'npm pack'
68 | *.tgz
69 |
70 | # Yarn Integrity file
71 | .yarn-integrity
72 |
73 | # dotenv environment variables file
74 | .env
75 | .env.test
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 |
80 | # Next.js build output
81 | .next
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 |
109 | # VSCode files
110 | sapling/out
111 | sapling/dist
112 | sapling/node_modules
113 | sapling/.vscode-test/
114 | *.vsix
--------------------------------------------------------------------------------
/flake-guard-npm/README.md:
--------------------------------------------------------------------------------
1 | # FlakeGuard
2 |
3 | FlakeGuard is a dev tool to detect flake and track test result metrics in Jest test suites.
4 |
5 | Visit flakeguard.com for more information or to create an account.
6 |
7 | This is a alpha version for a project in development through OSLabs.
8 |
9 | If you are interested in contributing, have feedback, or like talking about testing, please contact us.
10 |
11 | ## Quick Start
12 |
13 | Run this command to install FlakeGuard as a dev dependency in the project with your test files.
14 |
15 | ```
16 | npm i flake-guard --save-dev
17 | ````
18 |
19 | To run FlakeGuard, simply execute the command below with the name of the test file that you want to examine.
20 | ```
21 | npx flake-guard
22 | ```
23 | FlakeGuard will analyze your tests for flakiness by executing multiple test runs and analyzing the results.
24 |
25 | The default number of test runs is 10, but this can be adjusted as described below. In general, there is a time versus accuracy tradeoff. More runs makes FlakeGuard more accurate but slower.
26 |
27 | ## Configuration
28 |
29 | To adjust FlakeGuard configuration variables, you can create file in the root directory of your project called 'fg.config.json'.
30 |
31 | Below are the defaults, which can be overridden in your local 'fg.config.json' file.
32 |
33 | ```
34 | {
35 | runs: 10
36 | }
37 | ```
38 | For example, if you want to increase accuracy, you can increase test runs.
39 | ```
40 | {
41 | runs: 100
42 | }
43 | ```
44 |
45 | ## How It Works
46 | Under the hood, the flake-guard npm package is automating repeated runs of your test file. It will do a basic parsing of the results locally to log an object in your terminal with all of your test assertions and their pass/fail metrics. It sends off the raw Jest results objects to the FlakeGuard server for further analysis which you can view at flakeguard.com.
47 |
--------------------------------------------------------------------------------
/flake-guard-npm/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "runs": 10
3 | }
--------------------------------------------------------------------------------
/flake-guard-npm/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // ^ shebang to ensure the user can execute script from CL using node interpreter
3 |
4 | const {exec, spawn} = require('node:child_process');
5 | const {loadConfig} = require('./loadConfig');
6 | const readline = require('readline');
7 |
8 | // Set up configuration as defaults with user overrides
9 | const configObj = loadConfig();
10 | // Amount of runs specified in configuration
11 | const runTimes = configObj.runs;
12 | console.log(`Number of runs: ${runTimes}`);
13 |
14 | // Get test file from user's CLI command and build internal command
15 | const filename = process.argv[2];
16 | if (!filename) {
17 | throw new Error(
18 | 'Please provide a test file name to run FlakeGuard. ex "flake-guard .js"'
19 | );
20 | }
21 | const command = `jest ${filename} --json`;
22 |
23 | // Function to run the test file
24 | const runTest = () => {
25 | return new Promise(resolve => {
26 | exec(command, (error, stdout) => {
27 | resolve(stdout);
28 | });
29 | });
30 | };
31 |
32 | // Function to prompt the user to open dashboard
33 | function dashPrompt(url) {
34 | const rl = readline.createInterface({
35 | input: process.stdin,
36 | output: process.stdout,
37 | });
38 |
39 | console.log(
40 | 'Press Enter to open results in the dashboard, or Ctrl+C to exit...'
41 | );
42 | /* Determines the user's OS, generates the appropriate command, and executes the command when the user presses enter*/
43 | rl.on('line', async input => {
44 | if (input === '') {
45 | const command =
46 | process.platform === 'win32'
47 | ? 'start'
48 | : process.platform === 'darwin'
49 | ? 'open'
50 | : 'xdg-open';
51 | spawn(command, [url]);
52 | rl.close();
53 | }
54 | });
55 |
56 | /* If the user presses 'Ctrl + C'*/
57 | rl.on('SIGINT', () => {
58 | console.log('\nExiting...');
59 | rl.close();
60 | // eslint-disable-next-line no-process-exit
61 | process.exit();
62 | });
63 | }
64 |
65 | // Analyze the test by running it 'runTimes' amount of times
66 | const flakeGuard = async (iterations) => {
67 | const timestampStart = Date.now();
68 | const flakeGuardResults = {};
69 | const flakeGuardResultsVerbose = [];
70 | for (let i = 0; i < iterations; i++) {
71 | try {
72 | const result = await runTest();
73 | const parsedResult = JSON.parse(result);
74 | flakeGuardResultsVerbose.push(parsedResult);
75 | const {assertionResults} = parsedResult.testResults[0];
76 | // Build out the flakeGuardResults array
77 | assertionResults.forEach((assertion) => {
78 | if (
79 | !Object.prototype.hasOwnProperty.call(
80 | flakeGuardResults,
81 | assertion.fullName
82 | )
83 | ) {
84 | flakeGuardResults[assertion.fullName] = {passed: 0, failed: 0, skipped: 0};
85 | }
86 | if (assertion.status === 'passed') {
87 | flakeGuardResults[assertion.fullName].passed += 1;
88 | }
89 | else if (assertion.status === 'failed') flakeGuardResults[assertion.fullName].failed += 1
90 | else if (assertion.status === 'pending') flakeGuardResults[assertion.fullName].skipped += 1;
91 | });
92 | console.log(`Run ${i + 1} complete`);
93 | } catch (error) {
94 | console.error(`Error in run number ${i + 1}: ${error}`);
95 | }
96 | }
97 | // On completion, send results to FlakeGuard server
98 | try {
99 | // On completion, log runtime and website info to user's terminal
100 | const timestampEnd = Date.now();
101 | console.log(
102 | `Total FlakeGuard runtime: ${
103 | (timestampEnd - timestampStart) / 1000
104 | } seconds`
105 | );
106 | console.log('Results Summary:');
107 | console.log(flakeGuardResults);
108 | // Send results to Flake Guard App server
109 | const response = await fetch('https://flakeguard.com/results', {
110 | method: 'POST',
111 | headers: {
112 | 'Content-Type': 'application/JSON',
113 | },
114 | body: JSON.stringify({
115 | verbose: flakeGuardResultsVerbose,
116 | simple: flakeGuardResults,
117 | runTimes
118 | }),
119 | });
120 | const url = await response.json();
121 | console.log('Results successfully sent to FlakeGuard server');
122 | // Prompt user to press enter to open dashboard
123 | await dashPrompt(url);
124 | } catch (error) {
125 | console.error('Error sending results to FlakeGuard server: ', error);
126 | }
127 | };
128 |
129 | flakeGuard(runTimes);
130 |
--------------------------------------------------------------------------------
/flake-guard-npm/loadConfig.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | /* This function parses the default config object and then overrides the default with any user-designated properties */
5 | function loadConfig() {
6 | const defaultConfigPath = path.join(
7 | __dirname,
8 | './default.json'
9 | );
10 | let config = JSON.parse(
11 | fs.readFileSync(defaultConfigPath, 'utf8')
12 | );
13 |
14 | // Override with user's config settings if available
15 | const userConfigPath = path.join(process.cwd(), 'fg.config.json');
16 | if (fs.existsSync(userConfigPath)) {
17 | const externalConfig = JSON.parse(
18 | fs.readFileSync(userConfigPath, 'utf8')
19 | );
20 | config = {...config, ...externalConfig};
21 | }
22 |
23 | return config;
24 | }
25 |
26 | module.exports = { loadConfig };
--------------------------------------------------------------------------------
/flake-guard-npm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flake-guard",
3 | "version": "4.0.0",
4 | "description": "A dev tool for detecting flake and tracking metrics in Jest test suites.",
5 | "main": "index.js",
6 | "bin": {
7 | "flake-guard": "./index.js"
8 | },
9 | "keywords": ["flake", "jest", "flaky", "testing", "tests", "flaky tests", "test tracking", "test metrics"],
10 | "license": "ISC"
11 | }
12 |
--------------------------------------------------------------------------------