commit
462fb47b2b
|
@ -44,5 +44,9 @@ jobs:
|
|||
|
||||
- run: echo $ETH_WALLET_JSON > $ETH_WALLET_FILE
|
||||
|
||||
# run tests!
|
||||
- run: lein test
|
||||
- run: lein uberjar
|
||||
|
||||
- store_artifacts:
|
||||
path: target/uberjar/commiteth.jar
|
||||
destination: commiteth.jar
|
||||
|
|
|
@ -26,6 +26,7 @@ WORKDIR /root/
|
|||
RUN apt-get update
|
||||
RUN apt-get -y install xvfb
|
||||
RUN apt-get -y install wkhtmltopdf
|
||||
RUN apt-get -y install less
|
||||
|
||||
COPY --from=builder /usr/src/app/target/uberjar/commiteth.jar .
|
||||
COPY html2png.sh .
|
||||
|
|
|
@ -46,6 +46,7 @@ Key | Description
|
|||
--- | ---
|
||||
dev | Currently specifies whether Swagger UI endpoints should be added to routes
|
||||
port | HTTP port for the Ring web app
|
||||
dev-login | Local development only. Set it to GitHub name of your dev user in order to login into the system bypassing OAuth. `server-address` has to be then correspondingly set to your localhost address.
|
||||
nrepl-port | nREPL port for development
|
||||
jdbc-database-url | PostgreSQL database URL. For instance, URL to local db would be `jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth`
|
||||
server-address | URL and port of local server that can be resolved from public internet. It will be used as a redirect URI during GitHub OAuth authorization process
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Decisions
|
||||
|
||||
We record decisions. More context on why and how we do that can be found in [DR-0001](doc/decisions/0001-record-decisions.md).
|
||||
We record decisions. More context on why and how we do that can be found in [DR-0001](0001-record-decisions.md).
|
||||
|
||||
The remainder of this document is intended to document tooling around this process.
|
||||
|
||||
|
|
|
@ -1,50 +1,62 @@
|
|||
# Testing Open Bounty
|
||||
|
||||
We have a continuously deployed version tracking the `develop` branch live at https://openbounty.status.im:444. It uses the [Ropsten](https://ropsten.io/) Ethereum testnet. Any one is welcome to use it and your help with testing Open Bounty is greatly appreciated!
|
||||
We have two environments for testing purposes:
|
||||
* `staging` - where `develop` branch is continuously deployed
|
||||
|
||||
### General
|
||||
**URL:** https://openbounty.status.im:444
|
||||
|
||||
**GitHub app:** https://github.com/apps/status-open-bounty-app-test
|
||||
|
||||
* `testing`- where you can deploy and test separate pull requests
|
||||
|
||||
**URL:** https://testing.openbounty.status.im
|
||||
|
||||
**GitHub app:** https://github.com/apps/open-bounty-testing
|
||||
|
||||
Both of them use the [Ropsten](https://ropsten.io/) Ethereum testnet.
|
||||
Any one is welcome to use it and your help with testing Open Bounty is greatly appreciated!
|
||||
|
||||
|
||||
## General
|
||||
|
||||
For testing you will need:
|
||||
* a web browser (Chrome is known to work, testing with others appreciated)
|
||||
* an Ethereum account on the Ropsten testnet
|
||||
* a Github account with administrative access to one or more repositories
|
||||
* 2 Github accounts (one for Bounty Hunter, second - for Bounty Creator with administrative access to one or more repositories)
|
||||
* for approving bounty payouts you will additionally need access to an Ethereum wallet. ([MetaMask](https://metamask.io/) plugin)
|
||||
|
||||
The developers can be reached on the `#openbounty` channel in the [Status slack](http://slack.status.im/).
|
||||
|
||||
### Signing up
|
||||
|
||||
* point your browser to https://openbounty.status.im:444 and click `Login`
|
||||
Process is the same for Bounty Creator and Bounty Hunter.
|
||||
|
||||
* point your browser to URL of selected environment (`staging` or `testing`) and click `Login`
|
||||
* Authorise status-open-bounty to have read access to your public GitHub profile.
|
||||
|
||||
You should now see `Bounties`, `Activity`, `Repositories` and `Manage Payouts` tabs. In the upper right hand corner, there should be a dropdown with your GitHub username and options `My Payment Details` and `Sign Out`.
|
||||
You should now see `Bounties`, `Activity`, `Repositories` tabs. In the upper right hand corner, there should be a dropdown with your GitHub username and options `My Payment Details` and `Sign Out`.
|
||||
|
||||
## For Bounty Creator
|
||||
|
||||
### Connecting your wallet
|
||||
|
||||
(instructions for Metamask)
|
||||
* install Metamask and configure your account
|
||||
* select `My Payment Details` from the top-right dropdown, select the account you want to use from the selection list and click `Update`
|
||||
|
||||
|
||||
### Creating bounty issues
|
||||
|
||||
Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to https://github.com/apps/status-open-bounty-app-test (or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
|
||||
Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to **GitHub app** for selected environment(or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
|
||||
|
||||
* Request for your account to be whitelisted. Contact [Riot](https://chat.status.im) for more information
|
||||
* now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait`
|
||||
* once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code
|
||||
* in SOB `Manage Payouts` should appear (when you logged in)
|
||||
|
||||
|
||||
### Funding bounties
|
||||
|
||||
The Github comment has a QR code as an image containing the bounty contract address. The address is also on the comment as text. Use any ethereum wallet to send ETH and/or supported ERC20 tokens to this address. After a small delay (max 5 minutes), the activity feed should show that the related bounty issue's balance increased and comment should be updated.
|
||||
|
||||
### Submitting claims
|
||||
|
||||
To get bounties you need to provide an Ethereum address in you Payment details on the https://openbounty.status.im:444 that will be used to send bounties to.
|
||||
|
||||
Open a pull request against the target repository with `Fixes: #NN` in the comment where `NN` is the issue number of the bountied Github issue. After the PR has been opened, the activity feed should show an item indicating that your username has opened a claim for the related bounty issue. The repository admin should also see the claim under `Open claims` in the `Manage payouts` view.
|
||||
After this process bounty is available for BountyHunters on URL of selected environment in `Open Bounty` list.
|
||||
|
||||
### Managing payouts
|
||||
|
||||
|
@ -52,8 +64,20 @@ Repository admins see a listing of all open claims and bounties that have alread
|
|||
|
||||
### Removing bounties
|
||||
|
||||
To remove issue from the Bounties list you can close it in GitHub.
|
||||
To remove issue from the Bounties list you can close it in GitHub.
|
||||
|
||||
## For Bounty Hunter
|
||||
|
||||
### Submitting claims
|
||||
|
||||
Whole process is explained here: [Status Open Bounty Tutorial](https://www.youtube.com/watch?v=vTjcXP4kTHc).
|
||||
|
||||
To get bounties you need to provide an Ethereum address in you Payment details on the selected that will be used to send bounties to.
|
||||
|
||||
Open a pull request against the target repository with any keyword from [Closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/), i. e. `Fixes: #NN` in the comment where `NN` is the issue number of the bountied Github issue.
|
||||
After the PR has been opened, the `Activity` feed should show an item indicating that your username has opened a claim for the related bounty issue. The repository admin (Bounty Creator) should also see the claim under `Open claims` in the `Manage payouts` view.
|
||||
|
||||
Flow walkthrough for Bounty Creator and Bounty Contributor is [here](https://docs.google.com/presentation/d/1btWVeaqR6yPLSHHZQ2XgfgK8MU2tWuAtNUE_5hdfJCI/edit#slide=id.g314ca9a4e1_0_0).
|
||||
### Reporting bugs
|
||||
|
||||
All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues).
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
|
||||
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
|
||||
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_fkey;
|
||||
|
||||
|
||||
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id);
|
||||
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id);
|
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pkey;
|
||||
ALTER TABLE pull_requests DROP CONSTRAINT pull_requests_pr_id_key;
|
||||
|
||||
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pkey PRIMARY KEY (pr_id, issue_id);
|
||||
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_pr_id_key UNIQUE (pr_id, issue_id);
|
||||
ALTER TABLE pull_requests ADD CONSTRAINT pull_requests_fkey FOREIGN KEY (issue_id) REFERENCES issues(issue_id);
|
|
@ -43,6 +43,13 @@ SELECT *
|
|||
FROM users
|
||||
WHERE id = :id;
|
||||
|
||||
-- :name get-user-by-login :? :1
|
||||
-- :doc retrieve a user given GitHub login.
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE login = :login;
|
||||
|
||||
|
||||
-- :name get-repo-owner :? :1
|
||||
SELECT *
|
||||
FROM users u, repositories r
|
||||
|
@ -213,7 +220,7 @@ VALUES(:pr_id,
|
|||
:commit_sha,
|
||||
:user_id,
|
||||
:state)
|
||||
ON CONFLICT (pr_id) DO UPDATE
|
||||
ON CONFLICT (pr_id,issue_id) DO UPDATE
|
||||
SET
|
||||
state = :state,
|
||||
issue_number = :issue_number,
|
||||
|
|
|
@ -22,24 +22,22 @@
|
|||
|
||||
(defn deploy-contract [owner owner-address repo issue-id issue-number]
|
||||
(if (empty? owner-address)
|
||||
(log/error "Unable to deploy bounty contract because"
|
||||
"repo owner has no Ethereum addres")
|
||||
(log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id)
|
||||
(do
|
||||
(log/info "deploying contract to " owner-address)
|
||||
(log/infof "issue %s: Deploying contract to %s" issue-id owner-address)
|
||||
(if-let [transaction-hash (multisig/deploy-multisig owner-address)]
|
||||
(do
|
||||
(log/info "Contract deployed, transaction-hash:"
|
||||
transaction-hash)
|
||||
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id transaction-hash)
|
||||
(let [resp (github/post-deploying-comment owner
|
||||
repo
|
||||
issue-number
|
||||
transaction-hash)
|
||||
_ (log/info "post-deploying-comment response:" resp)
|
||||
comment-id (:id resp)]
|
||||
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
|
||||
(issues/update-comment-id issue-id comment-id))
|
||||
(issues/update-transaction-hash issue-id transaction-hash))
|
||||
(log/error "Failed to deploy contract to" owner-address)))))
|
||||
|
||||
(log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address)))))
|
||||
|
||||
(defn add-bounty-for-issue [repo repo-id issue]
|
||||
(let [{issue-id :id
|
||||
issue-number :number
|
||||
|
@ -47,16 +45,17 @@
|
|||
created-issue (issues/create repo-id issue-id issue-number issue-title)
|
||||
{owner-address :address
|
||||
owner :owner} (users/get-repo-owner repo-id)]
|
||||
(log/debug "Adding bounty for issue " repo issue-number "owner address: " owner-address)
|
||||
(log/debug "issue %s: Adding bounty for issue %s/%s - owner address: %s"
|
||||
issue-id repo issue-number owner-address)
|
||||
(if (= 1 created-issue)
|
||||
(deploy-contract owner owner-address repo issue-id issue-number)
|
||||
(log/debug "Issue already exists in DB, ignoring"))))
|
||||
(log/debug "issue %s: Issue already exists in DB, ignoring"))))
|
||||
|
||||
(defn maybe-add-bounty-for-issue [repo repo-id issue]
|
||||
(let [res (issues/get-issues-count repo-id)
|
||||
{count :count} res
|
||||
limit-reached? (> count max-issues-limit)
|
||||
_ (log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)]
|
||||
limit-reached? (> count max-issues-limit)]
|
||||
(log/debug "*** get-issues-count" repo-id " " res " " count " " limit-reached?)
|
||||
(if limit-reached?
|
||||
(log/debug "Total issues for repo limit reached " repo " " count)
|
||||
(add-bounty-for-issue repo repo-id issue))))
|
||||
|
@ -102,9 +101,7 @@
|
|||
(issues/get-issue-titles)]
|
||||
(let [gh-issue (github/get-issue owner repo issue_number)]
|
||||
(if-not (= title (:title gh-issue))
|
||||
(do
|
||||
(log/info "Updating changed title for issue" (:id gh-issue))
|
||||
(issues/update-issue-title (:id gh-issue) (:title gh-issue)))))))
|
||||
(issues/update-issue-title (:id gh-issue) (:title gh-issue))))))
|
||||
|
||||
(defn assert-keys [m ks]
|
||||
(doseq [k ks]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(ns commiteth.db.issues
|
||||
(:require [commiteth.db.core :refer [*db*] :as db]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[clojure.set :refer [rename-keys]]))
|
||||
[clojure.set :refer [rename-keys]]
|
||||
[clojure.tools.logging :as log]))
|
||||
|
||||
(defn create
|
||||
"Creates issue"
|
||||
|
@ -31,6 +32,7 @@
|
|||
|
||||
(defn update-issue-title
|
||||
[issue-id title]
|
||||
(log/info "issue %s: Updating changed title \"%s\"" issue-id title)
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/update-issue-title con-db {:issue_id issue-id
|
||||
:title title})))
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-user con-db {:id user-id})))
|
||||
|
||||
(defn get-user-by-login
|
||||
[login]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/get-user-by-login con-db {:login login})))
|
||||
|
||||
(defn exists?
|
||||
[user-id]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
|
|
|
@ -74,11 +74,19 @@
|
|||
signed (TransactionEncoder/signMessage tx (creds))
|
||||
hex-string (Numeric/toHexString signed)]
|
||||
hex-string))
|
||||
|
||||
(defn eth-gasstation-gas-price
|
||||
"Get max of average and average_calc from gas station and use that
|
||||
as gas price. average_calc is computed from a larger time period than average,
|
||||
so the idea is to account for both temporary dips (when average_calc > average)
|
||||
and temporary rises (average_calc < average) of gas price"
|
||||
[]
|
||||
(let [data (json-api-request "https://ethgasstation.info/json/ethgasAPI.json")
|
||||
avg-price (-> (get data "average")
|
||||
bigint)
|
||||
avg-price (max
|
||||
(-> (get data "average")
|
||||
bigint)
|
||||
(-> (get data "average_calc")
|
||||
bigint))
|
||||
avg-price-gwei (/ avg-price (bigint 10))]
|
||||
(->> (* (bigint (Math/pow 10 9)) avg-price-gwei) ;; for some reason the API returns 10x gwei price
|
||||
.toBigInteger)))
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
:on-testnet? (env :on-testnet)}))
|
||||
|
||||
(defn landing-page []
|
||||
(layout/render "index.html" {:authorize-url (github/signup-authorize-url)}))
|
||||
(layout/render "index.html"
|
||||
{:authorize-url (if (env :dev-login)
|
||||
(str (env :server-address) "/callback_dev")
|
||||
(github/signup-authorize-url))}))
|
||||
|
||||
(defn welcome-page []
|
||||
(layout/render "welcome.html"))
|
||||
|
|
|
@ -35,28 +35,33 @@
|
|||
|
||||
(defroutes redirect-routes
|
||||
(GET "/callback" [code state]
|
||||
(let [resp (github/post-for-token code state)
|
||||
body (keywordize-keys (codec/form-decode (:body resp)))
|
||||
scope (:scope body)
|
||||
access-token (:access_token body)]
|
||||
(log/info "access-token:" access-token)
|
||||
(log/debug "github sign-in callback, response body:" body)
|
||||
(if (:error body)
|
||||
;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error.
|
||||
(found (str (env :server-address) "/app"))
|
||||
(let [admin-token? (str/includes? scope "repo")
|
||||
token-key (if admin-token? :admin-token :token)
|
||||
gh-user (github/get-user access-token)
|
||||
new-user? (nil? (users/get-user (:id gh-user 0)))
|
||||
user (assoc (get-or-create-user access-token)
|
||||
token-key access-token)]
|
||||
(when (and (hubspot-contact-create-enabled)
|
||||
new-user?)
|
||||
(try
|
||||
(hubspot/create-hubspot-contact (:email user)
|
||||
(:name user "")
|
||||
(:login user))
|
||||
(catch Throwable t
|
||||
(log/error "Failed to create hubspot contact" t))))
|
||||
(assoc (found (str (env :server-address) "/app"))
|
||||
:session {:identity user}))))))
|
||||
(let [resp (github/post-for-token code state)
|
||||
body (keywordize-keys (codec/form-decode (:body resp)))
|
||||
scope (:scope body)
|
||||
access-token (:access_token body)]
|
||||
(log/info "access-token:" access-token)
|
||||
(log/debug "github sign-in callback, response body:" body)
|
||||
(if (:error body)
|
||||
;; Why does Mist browser sends two redirects at the same time? The latter results in 401 error.
|
||||
(found (str (env :server-address) "/app"))
|
||||
(let [admin-token? (str/includes? scope "repo")
|
||||
token-key (if admin-token? :admin-token :token)
|
||||
gh-user (github/get-user access-token)
|
||||
new-user? (nil? (users/get-user (:id gh-user 0)))
|
||||
user (assoc (get-or-create-user access-token)
|
||||
token-key access-token)]
|
||||
(when (and (hubspot-contact-create-enabled)
|
||||
new-user?)
|
||||
(try
|
||||
(hubspot/create-hubspot-contact (:email user)
|
||||
(:name user "")
|
||||
(:login user))
|
||||
(catch Throwable t
|
||||
(log/error "Failed to create hubspot contact" t))))
|
||||
(assoc (found (str (env :server-address) "/app"))
|
||||
:session {:identity user})))))
|
||||
(GET "/callback_dev" []
|
||||
(assoc (found (str (env :server-address) "/app"))
|
||||
:session {:identity (users/get-user-by-login (env :dev-login))}))
|
||||
|
||||
)
|
||||
|
|
|
@ -174,24 +174,23 @@
|
|||
pr-body :body
|
||||
pr-title :title} :pull_request}]
|
||||
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
|
||||
(if-let [issue (some->> (extract-issue-number owner repo pr-body pr-title)
|
||||
(first)
|
||||
(issues/get-issue repo-id))]
|
||||
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
|
||||
(do
|
||||
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
|
||||
(handle-claim issue
|
||||
user-id
|
||||
login name
|
||||
avatar_url
|
||||
owner repo
|
||||
repo-id
|
||||
pr-id
|
||||
pr-number
|
||||
head-sha
|
||||
merged?
|
||||
event-type))
|
||||
(log/info "PR for issue already merged"))
|
||||
(if-let [issues (remove nil? (map #(issues/get-issue repo-id %1) (extract-issue-number owner repo pr-body pr-title)))]
|
||||
(doseq [issue issues]
|
||||
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
|
||||
(do
|
||||
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
|
||||
(handle-claim issue
|
||||
user-id
|
||||
login name
|
||||
avatar_url
|
||||
owner repo
|
||||
repo-id
|
||||
pr-id
|
||||
pr-number
|
||||
head-sha
|
||||
merged?
|
||||
event-type))
|
||||
(log/info "PR for issue already merged")))
|
||||
(when (= :edited event-type)
|
||||
; Remove PR if it does not reference any issue
|
||||
(pull-requests/remove pr-id))))
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
(update-balances)))
|
||||
|
||||
)
|
||||
|
||||
(defn update-issue-contract-address
|
||||
"For each pending deployment: gets transaction receipt, updates db
|
||||
state (contract-address, comment-id) and posts github comment"
|
||||
|
@ -45,56 +46,57 @@
|
|||
(log/info "In update-issue-contract-address")
|
||||
(p :update-issue-contract-address
|
||||
(doseq [{issue-id :issue_id
|
||||
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
|
||||
(log/info "pending deployment:" transaction-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
|
||||
(log/info "update-issue-contract-address: transaction receipt for issue #"
|
||||
issue-id ": " receipt)
|
||||
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
|
||||
(let [issue (issues/update-contract-address issue-id contract-address)
|
||||
{owner :owner
|
||||
repo :repo
|
||||
comment-id :comment_id
|
||||
issue-number :issue_number} issue
|
||||
balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)]
|
||||
(log/info "Updating comment image")
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{})
|
||||
(log/info "Updating comment")
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{}))
|
||||
(log/error "Failed to find contract address in tx logs")))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-issue-contract-address exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex))))))
|
||||
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
|
||||
(log/infof "issue %s: pending deployment: %s" issue-id transaction-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt transaction-hash)]
|
||||
(log/infof "issue %s: update-issue-contract-address: tx receipt: %s" issue-id receipt)
|
||||
(if-let [contract-address (multisig/find-created-multisig-address receipt)]
|
||||
(let [issue (issues/update-contract-address issue-id contract-address)
|
||||
{owner :owner
|
||||
repo :repo
|
||||
comment-id :comment_id
|
||||
issue-number :issue_number} issue
|
||||
balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)]
|
||||
(log/infof "issue %s: Updating comment image" issue-id)
|
||||
(bounties/update-bounty-comment-image issue-id
|
||||
owner
|
||||
repo
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{})
|
||||
(log/infof "issue %s: Updating comment" issue-id)
|
||||
(github/update-comment owner
|
||||
repo
|
||||
comment-id
|
||||
issue-number
|
||||
contract-address
|
||||
balance-eth
|
||||
balance-eth-str
|
||||
{}))
|
||||
(log/errorf "issue %s: Failed to find contract address in tx logs" issue-id)))
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: update-issue-contract-address exception:" issue-id)))))
|
||||
(log/info "Exit update-issue-contract-address"))
|
||||
|
||||
|
||||
(defn deploy-pending-contracts
|
||||
"Under high-concurrency circumstances or in case geth is in defunct state, a bounty contract may not deploy successfully when the bounty label is addded to an issue. This function deploys such contracts."
|
||||
[]
|
||||
(p :deploy-pending-contracts
|
||||
(doseq [{issue-id :issue_id
|
||||
issue-number :issue_number
|
||||
owner :owner
|
||||
owner-address :owner_address
|
||||
repo :repo} (db-bounties/pending-contracts)]
|
||||
(log/debug "Trying to re-deploy failed bounty contract deployment, issue-id:" issue-id)
|
||||
(bounties/deploy-contract owner owner-address repo issue-id issue-number))))
|
||||
(p :deploy-pending-contracts
|
||||
(doseq [{issue-id :issue_id
|
||||
issue-number :issue_number
|
||||
owner :owner
|
||||
owner-address :owner_address
|
||||
repo :repo} (db-bounties/pending-contracts)]
|
||||
(log/infof "issue %s: Trying to re-deploy failed bounty contract deployment" issue-id)
|
||||
(try
|
||||
(bounties/deploy-contract owner owner-address repo issue-id issue-number)
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: deploy-pending-contracts exception:" issue-id))))))
|
||||
|
||||
(defn self-sign-bounty
|
||||
"Walks through all issues eligible for bounty payout and signs corresponding transaction"
|
||||
|
@ -110,37 +112,35 @@
|
|||
issue-number :issue_number
|
||||
balance-eth :balance_eth
|
||||
tokens :tokens
|
||||
winner-login :winner_login} (db-bounties/pending-bounties)]
|
||||
(try
|
||||
(let [value (eth/get-balance-hex contract-address)]
|
||||
(if (empty? payout-address)
|
||||
(do
|
||||
(log/error "Cannot sign pending bounty - winner has no payout address")
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
true))
|
||||
(let [execute-hash (multisig/send-all contract-address payout-address)]
|
||||
(log/info "Payout self-signed, called sign-all(" contract-address payout-address ") tx:" execute-hash)
|
||||
(db-bounties/update-execute-hash issue-id execute-hash)
|
||||
(db-bounties/update-winner-login issue-id winner-login)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
false))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "self-sign-bounty exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex))))))
|
||||
(log/info "Exit self-sign-bounty")
|
||||
)
|
||||
winner-login :winner_login} (db-bounties/pending-bounties)]
|
||||
(try
|
||||
(let [value (eth/get-balance-hex contract-address)]
|
||||
(if (empty? payout-address)
|
||||
(do
|
||||
(log/warn "issue %s: Cannot sign pending bounty - winner has no payout address" issue-id)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
true))
|
||||
(let [execute-hash (multisig/send-all contract-address payout-address)]
|
||||
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address execute-hash)
|
||||
(db-bounties/update-execute-hash issue-id execute-hash)
|
||||
(db-bounties/update-winner-login issue-id winner-login)
|
||||
(github/update-merged-issue-comment owner
|
||||
repo
|
||||
comment-id
|
||||
contract-address
|
||||
(eth-decimal->str balance-eth)
|
||||
tokens
|
||||
winner-login
|
||||
false))))
|
||||
(catch Throwable ex
|
||||
(log/error ex "issue %s: self-sign-bounty exception" issue-id)))))
|
||||
(log/info "Exit self-sign-bounty"))
|
||||
|
||||
(defn update-confirm-hash
|
||||
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
|
||||
|
@ -148,13 +148,16 @@
|
|||
(log/info "In update-confirm-hash")
|
||||
(p :update-confirm-hash
|
||||
(doseq [{issue-id :issue_id
|
||||
execute-hash :execute_hash} (db-bounties/pending-payouts)]
|
||||
(log/info "pending payout:" execute-hash)
|
||||
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
|
||||
(log/info "execution receipt for issue #" issue-id ": " receipt)
|
||||
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
|
||||
(log/info "confirm hash:" confirm-hash)
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash)))))
|
||||
execute-hash :execute_hash} (db-bounties/pending-payouts)]
|
||||
(log/infof "issue %s: pending payout: %s" issue-id execute-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
|
||||
(log/infof "issue %s: execution receipt for issue " issue-id receipt)
|
||||
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
|
||||
(log/infof "issue %s: confirm hash:" issue-id confirm-hash)
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash)))
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: update-confirm-hash exception:" issue-id)))))
|
||||
(log/info "Exit update-confirm-hash"))
|
||||
|
||||
|
||||
|
@ -163,10 +166,13 @@
|
|||
[]
|
||||
(p :update-watch-hash
|
||||
(doseq [{issue-id :issue_id
|
||||
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
|
||||
(log/info "pending watch call" watch-hash)
|
||||
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
|
||||
(db-bounties/update-watch-hash issue-id nil)))))
|
||||
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
|
||||
(log/infof "issue %s: pending watch call %s" issue-id watch-hash)
|
||||
(try
|
||||
(when-let [receipt (eth/get-transaction-receipt watch-hash)]
|
||||
(db-bounties/update-watch-hash issue-id nil))
|
||||
(catch Throwable ex
|
||||
(log/errorf ex "issue %s: update-watch-hash exception:" issue-id))))))
|
||||
|
||||
|
||||
(defn older-than-3h?
|
||||
|
@ -194,7 +200,7 @@
|
|||
confirm-id :confirm_hash
|
||||
payee-login :payee_login
|
||||
updated :updated} (db-bounties/confirmed-payouts)]
|
||||
(log/debug "confirmed payout:" payout-hash)
|
||||
(log/infof "issue %s: confirmed payout: %s" issue-id payout-hash)
|
||||
(try
|
||||
(if-let [receipt (eth/get-transaction-receipt payout-hash)]
|
||||
(let [contract-tokens (multisig/token-balances contract-address)
|
||||
|
@ -203,14 +209,14 @@
|
|||
(some #(> (second %) 0.0) contract-tokens)
|
||||
(> contract-eth-balance 0))
|
||||
(do
|
||||
(log/info "Contract still has funds")
|
||||
(log/infof "issue %s: Contract (%s) still has funds" issue-id contract-address)
|
||||
(when (multisig/is-confirmed? contract-address confirm-id)
|
||||
(log/info "Detected bounty with funds and confirmed payout, calling executeTransaction")
|
||||
(log/infof "issue %s: Detected bounty with funds and confirmed payout, calling executeTransaction" issue-id)
|
||||
(let [execute-tx-hash (multisig/execute-tx contract-address confirm-id)]
|
||||
(log/info "execute tx:" execute-tx-hash))))
|
||||
(log/infof "issue %s: execute tx: %s" issue-id execute-tx-hash))))
|
||||
|
||||
(do
|
||||
(log/info "Payout has succeeded, saving payout receipt for issue #" issue-id ": " receipt)
|
||||
(log/infof "issue %s: Payout has succeeded, payout receipt %s" issue-id receipt)
|
||||
(db-bounties/update-payout-receipt issue-id receipt)
|
||||
(github/update-paid-issue-comment owner
|
||||
repo
|
||||
|
@ -220,13 +226,11 @@
|
|||
tokens
|
||||
payee-login))))
|
||||
(when (older-than-3h? updated)
|
||||
(log/info "Resetting payout hash for issue" issue-id "as it has not been mined in 3h")
|
||||
(log/warn "issue %s: Resetting payout hash for issue as it has not been mined in 3h" issue-id)
|
||||
(db-bounties/reset-payout-hash issue-id)))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-payout-receipt exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex))))))
|
||||
(log/info "Exit update-payout-receipt")
|
||||
)
|
||||
(catch Throwable ex
|
||||
(log/error ex "issue %s: update-payout-receipt exception" issue-id)))))
|
||||
(log/info "Exit update-payout-receipt"))
|
||||
|
||||
(defn abs
|
||||
"(abs n) is the absolute value of n"
|
||||
|
@ -239,26 +243,27 @@
|
|||
|
||||
|
||||
(defn update-bounty-token-balances
|
||||
"Helper function for updating internal ERC20 token balances to token multisig contract. Will be called periodically for all open bounty contracts."
|
||||
"Helper function for updating internal ERC20 token balances to token
|
||||
multisig contract. Will be called periodically for all open bounty
|
||||
contracts."
|
||||
[issue-id bounty-addr watch-hash]
|
||||
#_(log/info "In update-bounty-token-balances for issue" issue-id)
|
||||
(log/info "In update-bounty-token-balances for issue" issue-id)
|
||||
(doseq [[tla token-data] (token-data/as-map)]
|
||||
(try
|
||||
(let [balance (multisig/token-balance bounty-addr tla)]
|
||||
(when (> balance 0)
|
||||
(do
|
||||
(log/info "bounty at" bounty-addr "has" balance "of token" tla)
|
||||
(log/infof "bounty %s: has %s of token %s" bounty-addr balance tla)
|
||||
(let [internal-balance (multisig/token-balance-in-bounty bounty-addr tla)]
|
||||
(when (and (nil? watch-hash)
|
||||
(not= balance internal-balance))
|
||||
(log/info "balances not in sync, calling watch")
|
||||
(log/infof "bounty %s: balances not in sync, calling watch" bounty-addr)
|
||||
(let [hash (multisig/watch-token bounty-addr tla)]
|
||||
(db-bounties/update-watch-hash issue-id hash)))))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-bounty-token-balances exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex)))))
|
||||
#_(log/info "Exit update-bounty-token-balances"))
|
||||
|
||||
(catch Throwable ex
|
||||
(log/error ex "bounty %s: update-bounty-token-balances exception" bounty-addr))))
|
||||
(log/info "Exit update-bounty-token-balances"))
|
||||
|
||||
|
||||
(defn update-contract-internal-balances
|
||||
"It is required in our current smart contract to manually update it's internal balance when some tokens have been added."
|
||||
|
@ -296,8 +301,8 @@
|
|||
[]
|
||||
(p :update-open-issue-usd-values
|
||||
(doseq [{bounty-addr :contract_address}
|
||||
(db-bounties/open-bounty-contracts)]
|
||||
(update-issue-usd-value bounty-addr))))
|
||||
(db-bounties/open-bounty-contracts)]
|
||||
(update-issue-usd-value bounty-addr))))
|
||||
|
||||
(defn float=
|
||||
([x y] (float= x y 0.0000001))
|
||||
|
@ -327,7 +332,7 @@
|
|||
(let [balance-eth-str (eth/get-balance-eth contract-address 6)
|
||||
balance-eth (read-string balance-eth-str)
|
||||
token-balances (multisig/token-balances contract-address)]
|
||||
(log/debug "update-balances" balance-eth
|
||||
(log/debug "issue" issue-id ": update-balances" balance-eth
|
||||
balance-eth-str token-balances owner repo issue-number)
|
||||
|
||||
(when (or
|
||||
|
@ -361,8 +366,7 @@
|
|||
token-balances)
|
||||
(update-issue-usd-value contract-address))))
|
||||
(catch Throwable ex
|
||||
(do (log/error "update-balances exception:" ex)
|
||||
(clojure.stacktrace/print-stack-trace ex))))))
|
||||
(log/error ex "issue %s: update-balances exception" issue-id)))))
|
||||
(log/info "Exit update-balances"))
|
||||
|
||||
|
||||
|
@ -370,7 +374,7 @@
|
|||
(try
|
||||
(func)
|
||||
(catch Throwable t
|
||||
(log/error t))))
|
||||
(log/error t (.getMessage t) (ex-data t)))))
|
||||
|
||||
(defn run-tasks [tasks]
|
||||
(doall
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
:as rf-storage
|
||||
:refer [reg-co-fx!]]
|
||||
[commiteth.ui-model :as ui-model]
|
||||
[commiteth.common :as common]))
|
||||
[commiteth.common :as common]
|
||||
[commiteth.routes :as routes]))
|
||||
|
||||
|
||||
(rf-storage/reg-co-fx! :commiteth-sob {:fx :store
|
||||
|
@ -40,6 +41,20 @@
|
|||
(println "redirecting to" path)
|
||||
(set! (.-pathname js/location) path)))
|
||||
|
||||
(reg-fx
|
||||
:persist-bounty-filters-in-query
|
||||
(fn [{:keys [bounty-filters]}]
|
||||
(let [query
|
||||
(->> bounty-filters
|
||||
(remove (comp nil? val))
|
||||
(map (fn [[k v]]
|
||||
[(ui-model/bounty-filter-type->query-param k)
|
||||
(ui-model/bounty-filter-value->query-param k v)]))
|
||||
(into {}))]
|
||||
(routes/nav! :bounties {} (if (= {} query)
|
||||
nil
|
||||
query)))))
|
||||
|
||||
(reg-event-fx
|
||||
:initialize-db
|
||||
[(inject-cofx :store)]
|
||||
|
@ -65,10 +80,17 @@
|
|||
|
||||
(reg-event-db
|
||||
:set-active-page
|
||||
(fn [db [_ page]]
|
||||
(fn [db [_ page params query]]
|
||||
(assoc db :page page
|
||||
:page-number 1
|
||||
::db/open-bounties-filters {}
|
||||
::db/open-bounties-filters
|
||||
(reduce-kv
|
||||
#(let [type (ui-model/query-param->bounty-filter-type %2)]
|
||||
(assoc %1
|
||||
type
|
||||
(ui-model/query-param->bounty-filter-value type %3)))
|
||||
{}
|
||||
query)
|
||||
::db/open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent)))
|
||||
|
||||
(reg-event-db
|
||||
|
@ -468,9 +490,12 @@
|
|||
(merge db {::db/open-bounties-sorting-type sorting-type
|
||||
:page-number 1})))
|
||||
|
||||
(reg-event-db
|
||||
::set-open-bounty-filter-type
|
||||
(fn [db [_ filter-type filter-value]]
|
||||
(-> db
|
||||
(assoc-in [::db/open-bounties-filters filter-type] filter-value)
|
||||
(assoc :page-number 1))))
|
||||
(reg-event-fx
|
||||
::set-open-bounty-filter-type
|
||||
(fn [{:keys [event db]} [_ filter-type filter-value]]
|
||||
(println "db" db)
|
||||
(let [filters (::db/open-bounties-filters db)]
|
||||
(println "filters" filters)
|
||||
{:persist-bounty-filters-in-query
|
||||
{:bounty-filters
|
||||
(assoc filters filter-type filter-value)}})))
|
||||
|
|
|
@ -14,12 +14,17 @@
|
|||
"A function which will be called on each route change."
|
||||
[name params query]
|
||||
(println "Route change to: " name params query)
|
||||
(rf/dispatch [:set-active-page name]))
|
||||
(rf/dispatch [:set-active-page name params query]))
|
||||
|
||||
(defn setup-nav! []
|
||||
(bide/start! router {:default :bounties
|
||||
:on-navigate on-navigate}))
|
||||
|
||||
(defn nav! [route-id]
|
||||
(bide/navigate! router route-id {}))
|
||||
(defn nav!
|
||||
([route-id]
|
||||
(nav! route-id nil))
|
||||
([route-id params]
|
||||
(nav! route-id params nil))
|
||||
([route-id params query]
|
||||
(bide/navigate! router route-id params query)))
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@
|
|||
(mapcat keys)
|
||||
(filter identity)
|
||||
set)]
|
||||
(into #{"ETH"} token-ids))))
|
||||
(into #{:ETH} token-ids))))
|
||||
|
||||
(reg-sub
|
||||
::filtered-and-sorted-open-bounties
|
||||
|
|
|
@ -87,11 +87,11 @@
|
|||
::bounty-filter-type.category ::bounty-filter-type-category|multiple-dynamic-options
|
||||
::bounty-filter-type.re-frame-subs-key-for-options :commiteth.subscriptions/open-bounties-currencies
|
||||
::bounty-filter-type.predicate (fn [filter-value bounty]
|
||||
(or (and (contains? filter-value "ETH")
|
||||
(or (and (contains? filter-value :ETH)
|
||||
(< 0 (js/parseFloat (:balance-eth bounty))))
|
||||
(not-empty
|
||||
(set/intersection
|
||||
(->> filter-value (remove #{"ETH"}) set)
|
||||
(->> filter-value (remove #{:ETH}) set)
|
||||
(-> bounty :tokens keys set)))))}
|
||||
|
||||
::bounty-filter-type|date
|
||||
|
@ -124,6 +124,50 @@
|
|||
(defn bounty-filter-type->name [filter-type]
|
||||
(-> bounty-filter-types-def (get filter-type) ::bounty-filter-type.name))
|
||||
|
||||
(defn bounty-filter-type->query-param [filter-type]
|
||||
(-> filter-type
|
||||
name
|
||||
(clojure.string/replace #".*\|" "")))
|
||||
|
||||
(defn bounty-filter-value->query-param [type value]
|
||||
(let [category (-> bounty-filter-types-def type ::bounty-filter-type.category)]
|
||||
(cond
|
||||
(= category ::bounty-filter-type-category|multiple-dynamic-options)
|
||||
(vec value)
|
||||
|
||||
(= category ::bounty-filter-type-category|single-static-option)
|
||||
(bounty-filter-type->query-param value)
|
||||
|
||||
(= category ::bounty-filter-type-category|range)
|
||||
(str (first value) "-" (second value)))))
|
||||
|
||||
(defn query-param->bounty-filter-type [query-param]
|
||||
(keyword (namespace ::_) (str "bounty-filter-type|" (name query-param))))
|
||||
|
||||
(defn query-param->bounty-filter-value [type value]
|
||||
(let [category (-> bounty-filter-types-def type ::bounty-filter-type.category)]
|
||||
(cond
|
||||
(= type ::bounty-filter-type|currency)
|
||||
(if (string? value)
|
||||
#{(keyword value)}
|
||||
(set (map keyword value)))
|
||||
|
||||
(= type ::bounty-filter-type|owner)
|
||||
(if (string? value)
|
||||
#{value}
|
||||
(set value))
|
||||
|
||||
(= type ::bounty-filter-type|claims)
|
||||
(keyword (namespace ::_)
|
||||
(str "bounty-filter-type-claims-option|" (name value)))
|
||||
|
||||
(= type ::bounty-filter-type|date)
|
||||
(keyword (namespace ::_)
|
||||
(str "bounty-filter-type-date-option|" (name value)))
|
||||
|
||||
(= category ::bounty-filter-type-category|range)
|
||||
(clojure.string/split value #"-"))))
|
||||
|
||||
(defn bounty-filter-value->short-text [filter-type filter-value]
|
||||
(cond
|
||||
(= filter-type ::bounty-filter-type|date)
|
||||
|
|
|
@ -2,11 +2,11 @@ node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') {
|
|||
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]])
|
||||
configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) {
|
||||
try {withCredentials([string(credentialsId: 'SOB_SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), string(credentialsId: 'SOB_SAUCE_USERNAME', variable: 'SAUCE_USERNAME')])
|
||||
{sh 'cd test && docker build -t end2end . && docker run --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
|
||||
{sh 'cd test && docker build -t end2end . && docker run -v `pwd`/end-to-end/tests/results:/app/results --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
|
||||
}
|
||||
finally {
|
||||
saucePublisher()
|
||||
junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/*.xml' }
|
||||
junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: 'test/end-to-end/tests/results/*.xml' }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ from selenium.webdriver.support import expected_conditions
|
|||
|
||||
|
||||
class BaseElement(object):
|
||||
|
||||
class Locator(object):
|
||||
|
||||
def __init__(self, by, value):
|
||||
|
|
|
@ -15,4 +15,3 @@ class BasePageObject(object):
|
|||
@property
|
||||
def time_now(self):
|
||||
return datetime.now().strftime('%-m%-d%-H%-M%-S')
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
from pages.base_page import BasePageObject
|
||||
from pages.base_element import *
|
||||
from tests import test_data
|
||||
import logging
|
||||
|
||||
|
||||
class ActivityDescription(BaseText):
|
||||
|
||||
def __init__(self, driver, status, issue_title):
|
||||
super(ActivityDescription, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector(
|
||||
'//div[@class="description"]/div[contains(.,"%s")]/a[contains(.,"%s")]' % (status, issue_title))
|
||||
|
||||
|
||||
class ActivityPage(BasePageObject):
|
||||
def __init__(self, driver):
|
||||
super(ActivityPage, self).__init__(driver)
|
||||
self.driver = driver
|
||||
|
||||
def get_activity_page(self):
|
||||
self.driver.get(test_data.config['Common']['url'] + 'app#/activity')
|
||||
|
||||
def check_activity_is_presented(self, status, issue_title):
|
||||
logging.info('Check that activity "%s %s" is displayed' % (status, issue_title))
|
||||
ActivityDescription(self.driver, status, issue_title).find_element()
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from pages.base_page import BasePageObject
|
||||
from pages.base_element import *
|
||||
from pages.base_element import BaseText
|
||||
from tests import test_data
|
||||
|
||||
|
||||
|
@ -16,6 +17,7 @@ class TopHuntersHeader(BaseText):
|
|||
super(TopHuntersHeader, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector('.top-hunters-header')
|
||||
|
||||
|
||||
class BountyTitles(BaseText):
|
||||
|
||||
def __init__(self, driver):
|
||||
|
@ -29,6 +31,7 @@ class BountyItemRows(BaseText):
|
|||
super(BountyItemRows, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector('.open-bounty-item-content .bounty-item-row')
|
||||
|
||||
|
||||
class BountyFooters(BaseText):
|
||||
|
||||
def __init__(self, driver):
|
||||
|
@ -36,6 +39,14 @@ class BountyFooters(BaseText):
|
|||
self.locator = self.Locator.css_selector('.open-bounty-item-content .footer-row')
|
||||
|
||||
|
||||
class BountyClaimsAmount(BaseText):
|
||||
|
||||
def __init__(self, driver, issue_title, claims_text):
|
||||
super(BaseText, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector(
|
||||
'//div[@class="header"]/a[contains(.,"%s")]/../../div[@class="footer-row"]/span[contains(.,"%s")]' % (issue_title, claims_text))
|
||||
|
||||
|
||||
class BountiesPage(BasePageObject):
|
||||
def __init__(self, driver):
|
||||
super(BountiesPage, self).__init__(driver)
|
||||
|
@ -51,3 +62,6 @@ class BountiesPage(BasePageObject):
|
|||
def get_bounties_page(self):
|
||||
self.driver.get(test_data.config['Common']['url'] + 'app')
|
||||
|
||||
def check_bounty_claims_amount(self, issue_title, claims_text):
|
||||
logging.info('Check that bounty "%s" has "%s"' % (issue_title, claims_text))
|
||||
BountyClaimsAmount(self.driver, issue_title, claims_text).find_element()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from pages.base_page import BasePageObject
|
||||
from pages.base_element import *
|
||||
from pages.base_element import BaseButton
|
||||
from tests import test_data
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import time, pytest
|
||||
from pages.base_element import *
|
||||
import time, pytest, git, os, shutil, logging
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
from pages.base_element import BaseEditBox, BaseButton, BaseText
|
||||
from pages.base_page import BasePageObject
|
||||
from tests import test_data
|
||||
from git import Repo
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
|
||||
|
||||
class EmailEditbox(BaseEditBox):
|
||||
|
@ -110,25 +107,30 @@ class ContractBody(BaseText):
|
|||
super(ContractBody, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector("//tbody//p[contains(text(), "
|
||||
"'Current balance: 0.000000 ETH')]")
|
||||
|
||||
|
||||
class IssueId(BaseText):
|
||||
def __init__(self, driver):
|
||||
super(IssueId, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector(".gh-header-number")
|
||||
|
||||
|
||||
class ForkButton(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(ForkButton, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector("[href='#fork-destination-box']")
|
||||
|
||||
|
||||
class HeaderInForkPopup(BaseText):
|
||||
def __init__(self, driver):
|
||||
super(HeaderInForkPopup, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector("#facebox-header")
|
||||
|
||||
|
||||
class UserAccountInForkPopup(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(UserAccountInForkPopup, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector("[value=%s]"%test_data.config['DEV']['gh_username'])
|
||||
self.locator = self.Locator.css_selector("[value=%s]" % test_data.config['DEV']['gh_username'])
|
||||
|
||||
|
||||
class ForkedRepoText(BaseText):
|
||||
|
@ -136,20 +138,45 @@ class ForkedRepoText(BaseText):
|
|||
super(ForkedRepoText, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector(".commit-tease")
|
||||
|
||||
|
||||
class DeleteRepo(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(DeleteRepo, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector("//button[text()[contains(.,' Delete this repository')]]")
|
||||
|
||||
|
||||
class RepoNameBoxInPopup(BaseEditBox):
|
||||
def __init__(self, driver):
|
||||
super(RepoNameBoxInPopup, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector("input[aria-label='Type in the name of the repository to confirm that you want to delete this repository.']")
|
||||
self.locator = self.Locator.css_selector(
|
||||
"input[aria-label='Type in the name of the repository to confirm that you want to delete this repository.']")
|
||||
|
||||
|
||||
class ConfirmDeleteButton(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(ConfirmDeleteButton, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector("//button[text()[contains(.,'I understand the consequences, delete')]]")
|
||||
self.locator = self.Locator.xpath_selector(
|
||||
"//button[text()[contains(.,'I understand the consequences, delete')]]")
|
||||
|
||||
|
||||
class CompareAndPullRequest(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(CompareAndPullRequest, self).__init__(driver)
|
||||
self.locator = self.Locator.css_selector(".RecentBranches a")
|
||||
|
||||
|
||||
class PrTitleEditBox(BaseEditBox):
|
||||
def __init__(self, driver):
|
||||
super(PrTitleEditBox, self).__init__(driver)
|
||||
self.locator = self.Locator.id("pull_request_body")
|
||||
|
||||
|
||||
class SubmitNewPrButton(BaseButton):
|
||||
def __init__(self, driver):
|
||||
super(SubmitNewPrButton, self).__init__(driver)
|
||||
self.locator = self.Locator.xpath_selector("//button[contains(text(), "
|
||||
"'Create pull request')]")
|
||||
|
||||
|
||||
class GithubPage(BasePageObject):
|
||||
def __init__(self, driver):
|
||||
|
@ -157,18 +184,18 @@ class GithubPage(BasePageObject):
|
|||
|
||||
self.driver = driver
|
||||
|
||||
# sign in and installation related
|
||||
self.email_input = EmailEditbox(self.driver)
|
||||
self.password_input = PasswordEditbox(self.driver)
|
||||
self.sign_in_button = SignInButton(self.driver)
|
||||
|
||||
self.authorize_sob = AuthorizeStatusOpenBounty(self.driver)
|
||||
self.permission_type = PermissionTypeText(self.driver)
|
||||
|
||||
self.install_button = InstallButton(self.driver)
|
||||
self.organization_button = OrganizationButton(self.driver)
|
||||
self.all_repositories_button = AllRepositoriesButton(self.driver)
|
||||
self.integration_permissions_group = IntegrationPermissionsGroup(self.driver)
|
||||
|
||||
# issue related
|
||||
self.new_issue_button = NewIssueButton(self.driver)
|
||||
self.issue_title_input = IssueTitleEditBox(self.driver)
|
||||
self.labels_button = LabelsButton(self.driver)
|
||||
|
@ -177,21 +204,28 @@ class GithubPage(BasePageObject):
|
|||
self.submit_new_issue_button = SubmitNewIssueButton(self.driver)
|
||||
self.contract_body = ContractBody(self.driver)
|
||||
self.issue_id = IssueId(self.driver)
|
||||
|
||||
# repo forking
|
||||
self.fork_button = ForkButton(self.driver)
|
||||
self.header_in_fork_popup = HeaderInForkPopup(self.driver)
|
||||
self.user_account_in_fork_popup = UserAccountInForkPopup(self.driver)
|
||||
self.forked_repo_text = ForkedRepoText(self.driver)
|
||||
|
||||
# repo deleting
|
||||
self.delete_repo = DeleteRepo(self.driver)
|
||||
self.repo_name_confirm_delete = RepoNameBoxInPopup(self.driver)
|
||||
self.confirm_delete = ConfirmDeleteButton(self.driver)
|
||||
|
||||
# PR related
|
||||
self.compare_and_pull_request = CompareAndPullRequest(self.driver)
|
||||
self.pr_body = PrTitleEditBox(self.driver)
|
||||
self.submit_new_pr_button = SubmitNewPrButton(self.driver)
|
||||
|
||||
def get_issues_page(self):
|
||||
self.driver.get(test_data.config['ORG']['gh_repo'] + 'issues')
|
||||
self.driver.get('%sissues' % test_data.config['ORG']['gh_repo'])
|
||||
|
||||
def get_issue_page(self, issue_id):
|
||||
self.driver.get(test_data.config['ORG']['gh_repo'] + 'issues/' + issue_id)
|
||||
self.driver.get('%sissues/%s' % (test_data.config['ORG']['gh_repo'], issue_id))
|
||||
|
||||
def get_sob_plugin_page(self):
|
||||
self.driver.get(test_data.config['Common']['sob_test_app'])
|
||||
|
@ -201,7 +235,6 @@ class GithubPage(BasePageObject):
|
|||
self.password_input.send_keys(password)
|
||||
self.sign_in_button.click()
|
||||
|
||||
|
||||
def install_sob_plugin(self):
|
||||
initial_url = self.driver.current_url
|
||||
self.get_sob_plugin_page()
|
||||
|
@ -215,14 +248,14 @@ class GithubPage(BasePageObject):
|
|||
self.get_issues_page()
|
||||
self.new_issue_button.click()
|
||||
test_data.issue = dict()
|
||||
test_data.issue['title'] = 'auto_test_bounty_%s' % self.time_now
|
||||
test_data.issue['title'] = 'auto_test_bounty_%s' % test_data.date_time
|
||||
self.issue_title_input.send_keys(test_data.issue['title'])
|
||||
self.labels_button.click()
|
||||
self.bounty_label.click()
|
||||
self.cross_button.click()
|
||||
self.submit_new_issue_button.click()
|
||||
test_data.issue['id'] = self.issue_id.text[1:]
|
||||
logging.info("Issue title is %s" % test_data.issue['title'])
|
||||
logging.info("Issue title is %s" % test_data.issue['title'])
|
||||
|
||||
def fork_repo(self, initial_repo, wait=60):
|
||||
self.driver.get(initial_repo)
|
||||
|
@ -242,35 +275,49 @@ class GithubPage(BasePageObject):
|
|||
except TimeoutException:
|
||||
time.sleep(10)
|
||||
pass
|
||||
pytest.fail('Contract is not deployed in %s minutes!' % str(wait/60))
|
||||
pytest.fail('Contract is not deployed in %s minutes!' % str(wait / 60))
|
||||
|
||||
#cloning via HTTPS
|
||||
def clone_repo(self, initial_repo=None, username=None, repo_name=None, repo_path='git_repo'):
|
||||
os.mkdir(repo_path)
|
||||
os.chdir(repo_path)
|
||||
test_data.local_repo_path = os.getcwd()
|
||||
# cloning via HTTPS
|
||||
def clone_repo(self, initial_repo=None, username=None, repo_name=None, repo_folder='test_repo'):
|
||||
os.mkdir(repo_folder)
|
||||
os.chdir(repo_folder)
|
||||
self.local_repo_path = os.getcwd()
|
||||
fork = 'https://github.com/%s/%s.git' % (username, repo_name)
|
||||
logging.info(('Cloning from %s to %s' % (fork, repo_path)))
|
||||
r = Repo.clone_from(fork, repo_path)
|
||||
logging.info(('Successefully cloned to: %s' % test_data.local_repo_path))
|
||||
logging.info('Set upstream to %s'% initial_repo)
|
||||
upstream = r.create_remote('upstream', initial_repo)
|
||||
logging.info(('Cloning from %s to %s' % (fork, self.local_repo_path)))
|
||||
repo = git.Repo.clone_from(fork, self.local_repo_path)
|
||||
logging.info(('Successefully cloned to: %s' % self.local_repo_path))
|
||||
logging.info('Set upstream to %s' % initial_repo)
|
||||
upstream = repo.create_remote('upstream', initial_repo)
|
||||
upstream.fetch()
|
||||
assert upstream.exists()
|
||||
r.heads.master.checkout()
|
||||
repo.heads.master.checkout()
|
||||
|
||||
def create_pr_git(self, branch, file_to_modify='test'):
|
||||
repo = git.Repo(self.local_repo_path)
|
||||
logging.info(repo.git.status())
|
||||
logging.info(repo.git.pull('upstream', 'master'))
|
||||
logging.info(repo.git.push('origin', 'master'))
|
||||
logging.info(repo.git.fetch('--all'))
|
||||
repo.git.checkout('-b', branch)
|
||||
file = open(os.path.join(self.local_repo_path, file_to_modify), 'w')
|
||||
file.write("Autotest change: %s \r \n" % test_data.date_time)
|
||||
logging.info(repo.git.add('test'))
|
||||
logging.info(repo.git.commit(m='Aut %s' % test_data.date_time))
|
||||
repo.git.push('origin', branch)
|
||||
|
||||
def open_pr_github(self, keyword_comment):
|
||||
self.get_url(test_data.config['DEV']['gh_forked_repo'])
|
||||
self.compare_and_pull_request.click()
|
||||
self.pr_body.send_keys('%s #%s' % (keyword_comment, test_data.issue['id']))
|
||||
self.submit_new_pr_button.click()
|
||||
|
||||
def clean_repo_local_folder(self):
|
||||
logging.info('Removing %s' % test_data.local_repo_path)
|
||||
if test_data.local_repo_path:
|
||||
shutil.rmtree(test_data.local_repo_path)
|
||||
logging.info('Removing %s' % self.local_repo_path)
|
||||
if self.local_repo_path:
|
||||
shutil.rmtree(self.local_repo_path)
|
||||
|
||||
def delete_fork(self):
|
||||
self.get_url(test_data.config['DEV']['gh_forked_repo'] + 'settings')
|
||||
self.delete_repo.click()
|
||||
self.repo_name_confirm_delete.send_keys(test_data.config['ORG']['gh_repo_name'])
|
||||
self.confirm_delete.click()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import time
|
||||
from pages.base_page import BasePageObject
|
||||
from pages.base_element import *
|
||||
from pages.base_element import BaseButton, BaseText, BaseEditBox
|
||||
from selenium.webdriver import ActionChains
|
||||
|
||||
|
||||
|
@ -74,7 +74,6 @@ class MetaMaskPlugin(BasePageObject):
|
|||
self.ok_button = OkButton(self.driver)
|
||||
|
||||
def recover_access(self, passphrase, password, confirm_password):
|
||||
|
||||
self.get_url('chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/popup.html')
|
||||
self.accept_button.click()
|
||||
ActionChains(self.driver).move_to_element(self.privacy_text.find_element()).perform()
|
||||
|
@ -85,4 +84,3 @@ class MetaMaskPlugin(BasePageObject):
|
|||
self.password_edit_box.send_keys(password)
|
||||
self.password_box_confirm.send_keys(confirm_password)
|
||||
self.ok_button.click()
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[pytest]
|
||||
norecursedirs = .git pages
|
||||
addopts = -s -v --junitxml=result.xml --tb=short
|
||||
addopts = -s -v --junitxml=results/result.xml --tb=short
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import configparser
|
||||
import os
|
||||
import configparser, time, datetime, os
|
||||
|
||||
|
||||
class TestData(object):
|
||||
|
||||
|
@ -7,15 +7,17 @@ class TestData(object):
|
|||
self.test_name = None
|
||||
self.config = configparser.ConfigParser()
|
||||
|
||||
# define here path to your config.ini file
|
||||
# example - config_example.ini
|
||||
# put config.ini to /test/end-to-end/tests folder (same directory where config_example.ini is placed
|
||||
self.tests_path = os.path.abspath(os.path.dirname(__file__))
|
||||
self.config.read(os.path.join(self.tests_path, 'config.ini'))
|
||||
|
||||
self.config.read(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'config.ini'))
|
||||
# create unique identificator for PRs, issues ect
|
||||
ts = time.time()
|
||||
st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.date_time = st
|
||||
|
||||
# self.issue['title'] is set in GithubPage::create_new_bounty
|
||||
# self.issue['id'] is set in GithubPage::create_new_bounty
|
||||
# self.local_repo_path is set in GithubPage::clone_repo
|
||||
|
||||
|
||||
|
||||
test_data = TestData()
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import pytest, sys
|
||||
import pytest, sys, os
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from tests.postconditions import remove_application, remove_installation
|
||||
from os import environ, path
|
||||
from os import environ
|
||||
from tests import test_data
|
||||
from pages.thirdparty.github import GithubPage
|
||||
from pages.openbounty.landing import LandingPage
|
||||
|
||||
class BaseTestCase:
|
||||
|
||||
class BaseTestCase:
|
||||
|
||||
def print_sauce_lab_info(self, driver):
|
||||
sys.stdout = sys.stderr
|
||||
print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id,
|
||||
pytest.config.getoption('build')))
|
||||
|
||||
def get_remote_caps(self):
|
||||
sauce_lab_cap = dict()
|
||||
sauce_lab_cap['name'] = test_data.test_name
|
||||
|
@ -36,11 +37,11 @@ class BaseTestCase:
|
|||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.errors = []
|
||||
cls.environment = pytest.config.getoption('env')
|
||||
cls.environment = pytest.config.getoption('env')
|
||||
|
||||
###################################################################################################################
|
||||
######### Drivers setup
|
||||
###################################################################################################################
|
||||
################################################################################################################
|
||||
######### Drivers setup
|
||||
################################################################################################################
|
||||
|
||||
#
|
||||
# Dev Chrome options
|
||||
|
@ -52,20 +53,20 @@ class BaseTestCase:
|
|||
# Org Chrome options
|
||||
#
|
||||
cls.capabilities_org = webdriver.ChromeOptions()
|
||||
# doesn't work on sauce env
|
||||
# cls.capabilities_org.add_extension(path.abspath(test_data.config['Paths']['tests_absolute'] + 'resources/metamask3_12_0.crx'))
|
||||
cls.capabilities_org.add_extension(
|
||||
os.path.join(test_data.tests_path, os.pardir, 'resources', 'metamask3_12_0.crx'))
|
||||
|
||||
#
|
||||
# SauceLab capabilities
|
||||
#
|
||||
cls.executor_sauce_lab = 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (
|
||||
environ.get('SAUCE_USERNAME'), environ.get('SAUCE_ACCESS_KEY'))
|
||||
drivers = []
|
||||
environ.get('SAUCE_USERNAME'), environ.get('SAUCE_ACCESS_KEY'))
|
||||
cls.drivers = []
|
||||
|
||||
if cls.environment == 'local':
|
||||
for caps in cls.capabilities_dev, cls.capabilities_org:
|
||||
driver = webdriver.Chrome(chrome_options=caps)
|
||||
drivers.append(driver)
|
||||
cls.drivers.append(driver)
|
||||
|
||||
if cls.environment == 'sauce':
|
||||
for caps in cls.capabilities_dev, cls.capabilities_org:
|
||||
|
@ -74,30 +75,26 @@ class BaseTestCase:
|
|||
new_caps.update(remote)
|
||||
driver = webdriver.Remote(cls.executor_sauce_lab,
|
||||
desired_capabilities=new_caps)
|
||||
drivers.append(driver)
|
||||
cls.drivers.append(driver)
|
||||
|
||||
for driver in drivers:
|
||||
cls.print_sauce_lab_info(cls, driver)
|
||||
cls.driver_dev = cls.drivers[0]
|
||||
cls.driver_org = cls.drivers[1]
|
||||
|
||||
cls.driver_dev = drivers[0]
|
||||
cls.driver_org = drivers[1]
|
||||
for driver in cls.drivers:
|
||||
driver.implicitly_wait(10)
|
||||
|
||||
|
||||
for driver in drivers:
|
||||
driver.implicitly_wait(10)
|
||||
|
||||
###################################################################################################################
|
||||
######### Actions for each driver before class
|
||||
###################################################################################################################
|
||||
################################################################################################################
|
||||
######### Actions for each driver before class
|
||||
################################################################################################################
|
||||
|
||||
######ORG
|
||||
landing = LandingPage(cls.driver_org)
|
||||
landing.get_landing_page()
|
||||
|
||||
# Sign Up to SOB
|
||||
# Sign Up to SOB
|
||||
cls.github_org = landing.login_button.click()
|
||||
cls.github_org.sign_in(test_data.config['ORG']['gh_login'],
|
||||
test_data.config['ORG']['gh_password'])
|
||||
test_data.config['ORG']['gh_password'])
|
||||
assert cls.github_org.permission_type.text == 'Personal user data'
|
||||
bounties_page = cls.github_org.authorize_sob.click()
|
||||
|
||||
|
@ -111,21 +108,17 @@ class BaseTestCase:
|
|||
# Sign In to GH as Developer
|
||||
cls.github_dev.get_login_page()
|
||||
cls.github_dev.sign_in(test_data.config['DEV']['gh_login'],
|
||||
test_data.config['DEV']['gh_password'])
|
||||
test_data.config['DEV']['gh_password'])
|
||||
|
||||
# Fork repo as Developer from Organization
|
||||
# Fork repo as Developer from Organization
|
||||
cls.github_dev.fork_repo(test_data.config['ORG']['gh_repo'])
|
||||
|
||||
# Cloning repo to local git as Developer and set upstream to Organization (via HTTPS)
|
||||
# Cloning repo to local git as Developer and set upstream to Organization (via HTTPS)
|
||||
cls.github_dev.clone_repo(test_data.config['ORG']['gh_repo'],
|
||||
test_data.config['DEV']['gh_username'],
|
||||
test_data.config['ORG']['gh_repo_name'],
|
||||
'git_repo')
|
||||
test_data.config['DEV']['gh_username'],
|
||||
test_data.config['ORG']['gh_repo_name'])
|
||||
cls.verify_no_errors(cls)
|
||||
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
|
||||
|
@ -140,10 +133,9 @@ class BaseTestCase:
|
|||
cls.github_dev.delete_fork()
|
||||
|
||||
try:
|
||||
cls.driver_dev.quit()
|
||||
cls.driver_org.quit()
|
||||
for driver in cls.drivers:
|
||||
cls.print_sauce_lab_info(cls, driver)
|
||||
driver.quit()
|
||||
|
||||
except WebDriverException:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -20,4 +20,3 @@ def remove_installation(driver):
|
|||
driver.find_element(By.CSS_SELECTOR, '.facebox-popup .btn-danger').click()
|
||||
except NoSuchElementException:
|
||||
pass
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import pytest
|
||||
from os import environ
|
||||
from pages.openbounty.landing import LandingPage
|
||||
from pages.openbounty.bounties import BountiesPage
|
||||
from pages.thirdparty.github import GithubPage
|
||||
from pages.openbounty.activity import ActivityPage
|
||||
from tests.basetestcase import BaseTestCase
|
||||
from tests import test_data
|
||||
|
||||
|
@ -10,18 +8,32 @@ from tests import test_data
|
|||
@pytest.mark.sanity
|
||||
class TestLogin(BaseTestCase):
|
||||
|
||||
def test_deploy_new_contract(self):
|
||||
def test_deploy_new_contract(self):
|
||||
|
||||
# Waiting for deployed contract; test_data.issue created here
|
||||
self.github_org.create_new_bounty()
|
||||
self.github_org.get_deployed_contract()
|
||||
|
||||
# Navigate and check top bounty in "Open bounties"
|
||||
bounties_page = BountiesPage(self.driver_org)
|
||||
bounties_page = BountiesPage(self.driver_dev)
|
||||
bounties_page.get_bounties_page()
|
||||
titles = bounties_page.bounty_titles.find_elements()
|
||||
assert titles[0].text == test_data.issue['title']
|
||||
|
||||
def test_new_claim(self):
|
||||
self.github_dev.create_pr_git('test_branch_%s' % self.github_dev.time_now)
|
||||
self.github_dev.open_pr_github('Fixes')
|
||||
|
||||
# check new claim in "Open bounties"
|
||||
bounties_page = BountiesPage(self.driver_dev)
|
||||
bounties_page.get_bounties_page()
|
||||
bounties_page.check_bounty_claims_amount(test_data.issue['title'], '1 open claim')
|
||||
|
||||
# check new claim in "Activity"
|
||||
activity_page = ActivityPage(self.driver_dev)
|
||||
activity_page.get_activity_page()
|
||||
activity_page.check_activity_is_presented('Submitted a claim for ', test_data.issue['title'])
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue