Skip to content

Commit 80a2a7f

Browse files
authored
ci: Make integrations tests more stable (#311)
Both the build Actor step and the run Actor step. Closes #301
1 parent e200a8e commit 80a2a7f

17 files changed

+550
-396
lines changed

.github/workflows/run_release.yaml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,11 @@ jobs:
6464
needs: [should_release]
6565
uses: ./.github/workflows/_version_conflict_check.yaml
6666

67-
# tmp disabled due to instability
68-
# integration_tests:
69-
# name: Integration tests
70-
# needs: [should_release]
71-
# uses: apify/workflows/.github/workflows/python_integration_tests.yaml@main
72-
# secrets: inherit
67+
integration_tests:
68+
name: Integration tests
69+
needs: [should_release]
70+
uses: apify/workflows/.github/workflows/python_integration_tests.yaml@main
71+
secrets: inherit
7372

7473
publish_to_pypi:
7574
name: Publish to PyPI
@@ -81,7 +80,7 @@ jobs:
8180
unit_tests,
8281
changelog_entry_check,
8382
version_conflict_check,
84-
# integration_tests, # tmp disabled due to instability
83+
integration_tests,
8584
]
8685
runs-on: ubuntu-latest
8786
permissions:

src/apify/scrapy/middlewares/apify_proxy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ApifyHttpProxyMiddleware:
2727
proxy_settings = {'useApifyProxy': true, 'apifyProxyGroups': []}
2828
"""
2929

30-
def __init__(self: ApifyHttpProxyMiddleware, proxy_settings: dict) -> None:
30+
def __init__(self, proxy_settings: dict) -> None:
3131
"""Create a new instance.
3232
3333
Args:
@@ -66,7 +66,7 @@ def from_crawler(cls: type[ApifyHttpProxyMiddleware], crawler: Crawler) -> Apify
6666

6767
return cls(proxy_settings)
6868

69-
async def process_request(self: ApifyHttpProxyMiddleware, request: Request, spider: Spider) -> None:
69+
async def process_request(self, request: Request, spider: Spider) -> None:
7070
"""Process a Scrapy request by assigning a new proxy.
7171
7272
Args:
@@ -89,7 +89,7 @@ async def process_request(self: ApifyHttpProxyMiddleware, request: Request, spid
8989
Actor.log.debug(f'ApifyHttpProxyMiddleware.process_request: updated request.meta={request.meta}')
9090

9191
def process_exception(
92-
self: ApifyHttpProxyMiddleware,
92+
self,
9393
request: Request,
9494
exception: Exception,
9595
spider: Spider,
@@ -116,7 +116,7 @@ def process_exception(
116116
'reason="{exception}", skipping...'
117117
)
118118

119-
async def _get_new_proxy_url(self: ApifyHttpProxyMiddleware) -> ParseResult:
119+
async def _get_new_proxy_url(self) -> ParseResult:
120120
"""Get a new proxy URL.
121121
122122
Raises:

src/apify/scrapy/pipelines/actor_dataset_push.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ActorDatasetPushPipeline:
1919
"""
2020

2121
async def process_item(
22-
self: ActorDatasetPushPipeline,
22+
self,
2323
item: Item,
2424
spider: Spider,
2525
) -> Item:

src/apify/scrapy/scheduler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ApifyScheduler(BaseScheduler):
2929
This scheduler requires the asyncio Twisted reactor to be installed.
3030
"""
3131

32-
def __init__(self: ApifyScheduler) -> None:
32+
def __init__(self) -> None:
3333
"""Create a new instance."""
3434
if not is_asyncio_reactor_installed():
3535
raise ValueError(
@@ -40,7 +40,7 @@ def __init__(self: ApifyScheduler) -> None:
4040
self._rq: RequestQueue | None = None
4141
self.spider: Spider | None = None
4242

43-
def open(self: ApifyScheduler, spider: Spider) -> None: # this has to be named "open"
43+
def open(self, spider: Spider) -> None: # this has to be named "open"
4444
"""Open the scheduler.
4545
4646
Args:
@@ -58,7 +58,7 @@ async def open_queue() -> RequestQueue:
5858
traceback.print_exc()
5959
raise
6060

61-
def has_pending_requests(self: ApifyScheduler) -> bool:
61+
def has_pending_requests(self) -> bool:
6262
"""Check if the scheduler has any pending requests.
6363
6464
Returns:
@@ -75,7 +75,7 @@ def has_pending_requests(self: ApifyScheduler) -> bool:
7575

7676
return not is_finished
7777

78-
def enqueue_request(self: ApifyScheduler, request: Request) -> bool:
78+
def enqueue_request(self, request: Request) -> bool:
7979
"""Add a request to the scheduler.
8080
8181
This could be called from either from a spider or a downloader middleware (e.g. redirect, retry, ...).
@@ -111,7 +111,7 @@ def enqueue_request(self: ApifyScheduler, request: Request) -> bool:
111111
Actor.log.debug(f'[{call_id}]: rq.add_request.result={result}...')
112112
return bool(result.was_already_present)
113113

114-
def next_request(self: ApifyScheduler) -> Request | None:
114+
def next_request(self) -> Request | None:
115115
"""Fetch the next request from the scheduler.
116116
117117
Returns:

tests/integration/README.md

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
Integration tests
2-
=================
1+
# Integration tests
32

4-
We have integration tests which build and run Actors using the Python SDK on the Apify Platform.
5-
To run these tests, you need to set the `APIFY_TEST_USER_API_TOKEN` environment variable to the API token of the Apify user you want to use for the tests,
6-
and then start them with `make integration-tests`.
3+
We have integration tests which build and run Actors using the Python SDK on the Apify Platform. To run these tests, you need to set the `APIFY_TEST_USER_API_TOKEN` environment variable to the API token of the Apify user you want to use for the tests, and then start them with `make integration-tests`.
74

8-
If you want to run the integration tests on a different environment than the main Apify Platform,
9-
you need to set the `APIFY_INTEGRATION_TESTS_API_URL` environment variable to the right URL to the Apify API you want to use.
5+
If you want to run the integration tests on a different environment than the main Apify Platform, you need to set the `APIFY_INTEGRATION_TESTS_API_URL` environment variable to the right URL to the Apify API you want to use.
106

11-
How to write tests
12-
------------------
7+
## How to write tests
138

149
There are two fixtures which you can use to write tests:
1510

1611
### `apify_client_async`
1712

18-
This fixture just gives you an instance of `ApifyClientAsync` configured with the right token and API URL,
19-
so you don't have to do that yourself.
13+
This fixture just gives you an instance of `ApifyClientAsync` configured with the right token and API URL, so you don't have to do that yourself.
2014

2115
```python
2216
async def test_something(apify_client_async: ApifyClientAsync) -> None:
@@ -27,64 +21,62 @@ async def test_something(apify_client_async: ApifyClientAsync) -> None:
2721

2822
This fixture returns a factory function for creating Actors on the Apify Platform.
2923

30-
For the Actor source, the fixture takes the files from `tests/integration/actor_source_base`,
31-
builds the Apify SDK wheel from the current codebase,
32-
and adds the Actor source you passed to the fixture as an argument.
33-
You have to pass exactly one of the `main_func`, `main_py` and `source_files` arguments.
24+
For the Actor source, the fixture takes the files from `tests/integration/actor_source_base`, builds the Apify SDK wheel from the current codebase, and adds the Actor source you passed to the fixture as an argument. You have to pass exactly one of the `main_func`, `main_py` and `source_files` arguments.
3425

35-
The created Actor will be uploaded to the platform, built there, and after the test finishes, it will be automatically deleted.
36-
If the Actor build fails, it will not be deleted, so that you can check why the build failed.
26+
The created Actor will be uploaded to the platform, built there, and after the test finishes, it will be automatically deleted. If the Actor build fails, it will not be deleted, so that you can check why the build failed.
3727

3828
### Creating test Actor straight from a Python function
3929

40-
You can create Actors straight from a Python function.
41-
This is great because you can have the test Actor source code checked with the linter.
30+
You can create Actors straight from a Python function. This is great because you can have the test Actor source code checked with the linter.
4231

4332
```python
44-
async def test_something(self, make_actor: ActorFactory) -> None:
33+
async def test_something(
34+
make_actor: MakeActorFunction,
35+
run_actor: RunActorFunction,
36+
) -> None:
4537
async def main() -> None:
4638
async with Actor:
4739
print('Hello!')
4840

49-
actor = await make_actor('something', main_func=main)
41+
actor = await make_actor(label='something', main_func=main)
42+
run_result = await run_actor(actor)
5043

51-
run_result = await actor.call()
52-
53-
assert run_result is not None
54-
assert run_result['status'] == 'SUCCEEDED'
44+
assert run_result.status == 'SUCCEEDED'
5545
```
5646

57-
These Actors will have the `src/main.py` file set to the `main` function definition,
58-
prepended with `import asyncio` and `from apify import Actor`, for your convenience.
47+
These Actors will have the `src/main.py` file set to the `main` function definition, prepended with `import asyncio` and `from apify import Actor`, for your convenience.
5948

6049
You can also pass extra imports directly to the main function:
6150

6251
```python
63-
async def test_something(self, make_actor: ActorFactory) -> None:
52+
async def test_something(
53+
make_actor: MakeActorFunction,
54+
run_actor: RunActorFunction,
55+
) -> None:
6456
async def main():
6557
import os
6658
from apify_shared.consts import ActorEventTypes, ActorEnvVars
6759
async with Actor:
6860
print('The Actor is running with ' + os.getenv(ActorEnvVars.MEMORY_MBYTES) + 'MB of memory')
6961
await Actor.on(ActorEventTypes.SYSTEM_INFO, lambda event_data: print(event_data))
7062

71-
actor = await make_actor('something', main_func=main)
72-
73-
run_result = await actor.call()
63+
actor = await make_actor(label='something', main_func=main)
64+
run_result = await run_actor(actor)
7465

75-
assert run_result is not None
76-
assert run_result['status'] == 'SUCCEEDED'
66+
assert run_result.status == 'SUCCEEDED'
7767
```
7868

7969
### Creating Actor from source files
8070

81-
You can also pass the source files directly if you need something more complex
82-
(e.g. pass some fixed value to the Actor source code or use multiple source files).
71+
You can also pass the source files directly if you need something more complex (e.g. pass some fixed value to the Actor source code or use multiple source files).
8372

8473
To pass the source code of the `src/main.py` file directly, use the `main_py` argument to `make_actor`:
8574

8675
```python
87-
async def test_something(self, make_actor: ActorFactory) -> None:
76+
async def test_something(
77+
make_actor: MakeActorFunction,
78+
run_actor: RunActorFunction,
79+
) -> None:
8880
expected_output = f'ACTOR_OUTPUT_{crypto_random_object_id(5)}'
8981
main_py_source = f"""
9082
import asyncio
@@ -96,21 +88,22 @@ async def test_something(self, make_actor: ActorFactory) -> None:
9688
await Actor.set_value('OUTPUT', '{expected_output}')
9789
"""
9890

99-
actor = await make_actor('something', main_py=main_py_source)
100-
101-
await actor.call()
91+
actor = await make_actor(label='something', main_py=main_py_source)
92+
await run_actor(actor)
10293

10394
output_record = await actor.last_run().key_value_store().get_record('OUTPUT')
10495
assert output_record is not None
10596
assert output_record['value'] == expected_output
10697

10798
```
10899

109-
Or you can pass multiple source files with the `source_files` argument,
110-
if you need something really complex:
100+
Or you can pass multiple source files with the `source_files` argument, if you need something really complex:
111101

112102
```python
113-
async def test_something(self, make_actor: ActorFactory) -> None:
103+
async def test_something(
104+
make_actor: MakeActorFunction,
105+
run_actor: RunActorFunction,
106+
) -> None:
114107
actor_source_files = {
115108
'src/utils.py': """
116109
from datetime import datetime, timezone
@@ -129,9 +122,8 @@ async def test_something(self, make_actor: ActorFactory) -> None:
129122
print('Hello! It is ' + current_datetime.time())
130123
""",
131124
}
132-
actor = await make_actor('something', source_files=actor_source_files)
125+
actor = await make_actor(label='something', source_files=actor_source_files)
126+
actor_run = await run_actor(actor)
133127

134-
actor_run = await actor.call()
135-
assert actor_run is not None
136-
assert actor_run['status'] == 'SUCCEEDED'
128+
assert actor_run.status == 'SUCCEEDED'
137129
```

0 commit comments

Comments
 (0)