{"id":2123,"url":"https://github.com/ashfurrow/second_curtain","last_synced_at":"2025-10-06T15:56:52.401Z","repository":{"id":18918953,"uuid":"22137935","full_name":"ashfurrow/second_curtain","owner":"ashfurrow","description":"Upload failing iOS snapshot tests cases to S3","archived":false,"fork":false,"pushed_at":"2015-11-23T20:39:34.000Z","size":202,"stargazers_count":131,"open_issues_count":11,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-08-29T20:22:37.337Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ashfurrow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-23T08:17:06.000Z","updated_at":"2024-11-27T18:44:42.000Z","dependencies_parsed_at":"2022-08-21T01:50:54.618Z","dependency_job_id":null,"html_url":"https://github.com/ashfurrow/second_curtain","commit_stats":null,"previous_names":["ashfurrow/upload-ios-snapshot-test-case"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/ashfurrow/second_curtain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashfurrow%2Fsecond_curtain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashfurrow%2Fsecond_curtain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashfurrow%2Fsecond_curtain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashfurrow%2Fsecond_curtain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ashfurrow","download_url":"https://codeload.github.com/ashfurrow/second_curtain/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ashfurrow%2Fsecond_curtain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277866767,"owners_count":25891280,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-01T02:00:09.286Z","response_time":88,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:16:05.140Z","updated_at":"2025-10-06T15:56:52.371Z","avatar_url":"https://github.com/ashfurrow.png","language":"Objective-C","funding_links":[],"categories":["Testing"],"sub_categories":["Other Testing","Other free courses"],"readme":"Second Curtain\n=============================\n\n[![Build Status](https://travis-ci.org/ashfurrow/second_curtain.svg?branch=master)](https://travis-ci.org/ashfurrow/second_curtain)\n\nIf you're using the cool [FBSnapshotTestCase](https://github.com/facebook/ios-snapshot-test-case) to test your iOS view logic, awesome! Even better if you have continuous integration, like on [Travis](https://travis-ci.org), to automate running those tests!\n\nPurpose\n----------------\n\nIsn't it frustrating that you can't *see* the results of your tests? At best, you'll get this kind of error output:\n\n``` sh\nASHViewControllerSpec\n  a_view_controller_with_a_loaded_view_should_have_a_valid_snapshot, expected a matching snapshot in a_view_controller_with_a_loaded_view_should_have_a_valid_snapshot\n  /Users/travis/build/AshFurrow/upload-ios-snapshot-test-case/Demo/DemoTests/DemoTests.m:31\n\n        it(@\"should have a valid snapshot\", ^{\n            expect(viewController).to.haveValidSnapshot();\n        });\n\n    Executed 1 test, with 1 failure (1 unexpected) in 0.952 (0.954) seconds\n** TEST FAILED **\n```\n\nWouldn't it be awesome if we could upload the failing test snapshots somewhere, so we can see exactly what's wrong? That's what we aim to do here.\n\nUsage\n----------------\n\nUsage is pretty simple. Have an S3 bucket that is world-readable (that is, include the following bucket policy).\n\n``` json\n{\n  \"Version\": \"2008-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"AllowPublicRead\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"*\"\n      },\n      \"Action\": \"s3:GetObject\",\n      \"Resource\": \"arn:aws:s3:::bucket-name/*\"\n    }\n  ]\n}\n```\n\n(Replace \"bucket-name\" with your bucket name.)\n\nIt's also a good idea not to use a general-purpose S3 user for your CI, so create a new one with the following policy that will let them list buckets, but only read from or write to the bucket you're using.\n\n``` json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:ListAllMyBuckets\"],\n      \"Resource\": \"arn:aws:s3:::*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"s3:*\"\n      ],\n      \"Resource\": \"arn:aws:s3:::bucket-name\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"s3:*\"\n      ],\n      \"Resource\": \"arn:aws:s3:::bucket-name/*\"\n    }\n  ]\n}\n```\n\nOK, so the hard part is mostly done. Now that we have a place to upload our images, let's configure our build.\n\nI use a Makefile to build and run tests on my iOS project, so my `.travis.yml` file looks something like this:\n\n``` ruby\nlanguage: objective-c\ncache: bundler\n\nenv:\n  - UPLOAD_IOS_SNAPSHOT_BUCKET_NAME=bucket-name\n  - UPLOAD_IOS_SNAPSHOT_BUCKET_PREFIX=an/optional/prefix\n  - AWS_ACCESS_KEY_ID=ACCESS_KEY\n  - AWS_SECRET_ACCESS_KEY=SECRET_KEY\n  - AWS_REGION=OPTIONAL_REGION_DEFINITION\n\nbefore_install:\n  - bundle install\n\nbefore_script:\n  - export LANG=en_US.UTF-8\n  - make ci\n\nscript:\n  - make test\n```\n\n(You can take a look at how to [encrypt information in your config file](http://docs.travis-ci.com/user/encryption-keys/), but this has limitations due to how ecrypted variables are accessed via PRs on forks.)\n\nMy Makefile looks like this:\n\n``` sh\nWORKSPACE = Demo/Demo.xcworkspace\nSCHEME = Demo\n\nall: ci\n\nbuild:\n\tset -o pipefail \u0026\u0026 xcodebuild -workspace $(WORKSPACE) -scheme $(SCHEME) -sdk iphonesimulator build | xcpretty -c\n\nclean:\n\txcodebuild -workspace $(WORKSPACE) -scheme $(SCHEME) clean\n\ntest:\n\tset -o pipefail \u0026\u0026 xcodebuild -workspace $(WORKSPACE) -scheme $(SCHEME) -configuration Debug test -sdk iphonesimulator | second_curtain | xcpretty -c --test\n\nci:\tbuild\n```\n\nNotice that we're piping the output from `xcodebuild` into `second_curtain`.\n\nNote also that we're using [`xcpretty`](http://github.com/supermarin/xcpretty), as you should, too. The `xcpretty` invocation must come *after* the `second_curtain` invocation, since Second Curtain relies on parsing the output from `xcodebuild` directly.\n\nAnd finally, our Gemfile:\n\n```\nsource 'https://rubygems.org'\n\ngem 'cocoapods'\ngem 'xcpretty'\ngem 'second_curtain', '~\u003e 0.2'\n```\n\nAnd when any snapshot tests fail, they'll be uploaded to S3 and an [HTML page](https://eigen-ci.s3.amazonaws.com/snapshots/2014-08-04--15-47/index.html) will be generated with links to the images so you can download them. Huzzah!\n\n![Sample diff](http://static.ashfurrow.com/github/second_curtain.png)\n\nNote that when the S3 directory is created, it needs to be *unique*. You can provide a custom folder name with the `UPLOAD_IOS_SNAPSHOT_FOLDER_NAME` environment variable. If one is not provided, Second Curtain will fall back to `TRAVIS_JOB_ID` or `CIRCLE_BUILD_NUM`, and then onto one generated by the current date and time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashfurrow%2Fsecond_curtain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fashfurrow%2Fsecond_curtain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashfurrow%2Fsecond_curtain/lists"}